Codebase list matrix-synapse / upstream/1.35.0
New upstream version 1.35.0 Andrej Shadura 2 years ago
100 changed file(s) with 6556 addition(s) and 2159 deletion(s). Raw diff Collapse all Expand all
22 # CI's Docker setup at the point where this file is considered.
33 server_name: "localhost:8800"
44
5 signing_key_path: "/src/.buildkite/test.signing.key"
5 signing_key_path: ".buildkite/test.signing.key"
66
77 report_stats: false
88
1515 database: synapse
1616
1717 # Suppress the key server warning.
18 trusted_key_servers:
19 - server_name: "matrix.org"
20 suppress_key_server_warning: true
18 trusted_key_servers: []
3232 echo "+++ Run synapse_port_db against test database"
3333 coverage run scripts/synapse_port_db --sqlite-database .buildkite/test_db.db --postgres-config .buildkite/postgres-config.yaml
3434
35 # We should be able to run twice against the same database.
36 echo "+++ Run synapse_port_db a second time"
37 coverage run scripts/synapse_port_db --sqlite-database .buildkite/test_db.db --postgres-config .buildkite/postgres-config.yaml
38
3539 #####
3640
3741 # Now do the same again, on an empty database.
22 # schema and run background updates on it.
33 server_name: "localhost:8800"
44
5 signing_key_path: "/src/.buildkite/test.signing.key"
5 signing_key_path: ".buildkite/test.signing.key"
66
77 report_stats: false
88
1212 database: ".buildkite/test_db.db"
1313
1414 # Suppress the key server warning.
15 trusted_key_servers:
16 - server_name: "matrix.org"
17 suppress_key_server_warning: true
15 trusted_key_servers: []
0 Synapse 1.35.0 (2021-06-01)
1 ===========================
2
3 Note that [the tag](https://github.com/matrix-org/synapse/releases/tag/v1.35.0rc3) and [docker images](https://hub.docker.com/layers/matrixdotorg/synapse/v1.35.0rc3/images/sha256-34ccc87bd99a17e2cbc0902e678b5937d16bdc1991ead097eee6096481ecf2c4?context=explore) for `v1.35.0rc3` were incorrectly built. If you are experiencing issues with either, it is recommended to upgrade to the equivalent tag or docker image for the `v1.35.0` release.
4
5 Deprecations and Removals
6 -------------------------
7
8 - The core Synapse development team plan to drop support for the [unstable API of MSC2858](https://github.com/matrix-org/matrix-doc/blob/master/proposals/2858-Multiple-SSO-Identity-Providers.md#unstable-prefix), including the undocumented `experimental.msc2858_enabled` config option, in August 2021. Client authors should ensure that their clients are updated to use the stable API (which has been supported since Synapse 1.30) well before that time, to give their users time to upgrade. ([\#10101](https://github.com/matrix-org/synapse/issues/10101))
9
10 Bugfixes
11 --------
12
13 - Fixed a bug causing replication requests to fail when receiving a lot of events via federation. Introduced in v1.33.0. ([\#10082](https://github.com/matrix-org/synapse/issues/10082))
14 - Fix HTTP response size limit to allow joining very large rooms over federation. Introduced in v1.33.0. ([\#10093](https://github.com/matrix-org/synapse/issues/10093))
15
16
17 Internal Changes
18 ----------------
19
20 - Log method and path when dropping request due to size limit. ([\#10091](https://github.com/matrix-org/synapse/issues/10091))
21
22
23 Synapse 1.35.0rc2 (2021-05-27)
24 ==============================
25
26 Bugfixes
27 --------
28
29 - Fix a bug introduced in v1.35.0rc1 when calling the spaces summary API via a GET request. ([\#10079](https://github.com/matrix-org/synapse/issues/10079))
30
31
32 Synapse 1.35.0rc1 (2021-05-25)
33 ==============================
34
35 Features
36 --------
37
38 - Add experimental support to allow a user who could join a restricted room to view it in the spaces summary. ([\#9922](https://github.com/matrix-org/synapse/issues/9922), [\#10007](https://github.com/matrix-org/synapse/issues/10007), [\#10038](https://github.com/matrix-org/synapse/issues/10038))
39 - Reduce memory usage when joining very large rooms over federation. ([\#9958](https://github.com/matrix-org/synapse/issues/9958))
40 - Add a configuration option which allows enabling opentracing by user id. ([\#9978](https://github.com/matrix-org/synapse/issues/9978))
41 - Enable experimental support for [MSC2946](https://github.com/matrix-org/matrix-doc/pull/2946) (spaces summary API) and [MSC3083](https://github.com/matrix-org/matrix-doc/pull/3083) (restricted join rules) by default. ([\#10011](https://github.com/matrix-org/synapse/issues/10011))
42
43
44 Bugfixes
45 --------
46
47 - Fix a bug introduced in v1.26.0 which meant that `synapse_port_db` would not correctly initialise some postgres sequences, requiring manual updates afterwards. ([\#9991](https://github.com/matrix-org/synapse/issues/9991))
48 - Fix `synctl`'s `--no-daemonize` parameter to work correctly with worker processes. ([\#9995](https://github.com/matrix-org/synapse/issues/9995))
49 - Fix a validation bug introduced in v1.34.0 in the ordering of spaces in the space summary API. ([\#10002](https://github.com/matrix-org/synapse/issues/10002))
50 - Fixed deletion of new presence stream states from database. ([\#10014](https://github.com/matrix-org/synapse/issues/10014), [\#10033](https://github.com/matrix-org/synapse/issues/10033))
51 - Fixed a bug with very high resolution image uploads throwing internal server errors. ([\#10029](https://github.com/matrix-org/synapse/issues/10029))
52
53
54 Updates to the Docker image
55 ---------------------------
56
57 - Fix bug introduced in Synapse 1.33.0 which caused a `Permission denied: '/homeserver.log'` error when starting Synapse with the generated log configuration. Contributed by Sergio Miguéns Iglesias. ([\#10045](https://github.com/matrix-org/synapse/issues/10045))
58
59
60 Improved Documentation
61 ----------------------
62
63 - Add hardened systemd files as proposed in [#9760](https://github.com/matrix-org/synapse/issues/9760) and added them to `contrib/`. Change the docs to reflect the presence of these files. ([\#9803](https://github.com/matrix-org/synapse/issues/9803))
64 - Clarify documentation around SSO mapping providers generating unique IDs and localparts. ([\#9980](https://github.com/matrix-org/synapse/issues/9980))
65 - Updates to the PostgreSQL documentation (`postgres.md`). ([\#9988](https://github.com/matrix-org/synapse/issues/9988), [\#9989](https://github.com/matrix-org/synapse/issues/9989))
66 - Fix broken link in user directory documentation. Contributed by @junquera. ([\#10016](https://github.com/matrix-org/synapse/issues/10016))
67 - Add missing room state entry to the table of contents of room admin API. ([\#10043](https://github.com/matrix-org/synapse/issues/10043))
68
69
70 Deprecations and Removals
71 -------------------------
72
73 - Removed support for the deprecated `tls_fingerprints` configuration setting. Contributed by Jerin J Titus. ([\#9280](https://github.com/matrix-org/synapse/issues/9280))
74
75
76 Internal Changes
77 ----------------
78
79 - Allow sending full presence to users via workers other than the one that called `ModuleApi.send_local_online_presence_to`. ([\#9823](https://github.com/matrix-org/synapse/issues/9823))
80 - Update comments in the space summary handler. ([\#9974](https://github.com/matrix-org/synapse/issues/9974))
81 - Minor enhancements to the `@cachedList` descriptor. ([\#9975](https://github.com/matrix-org/synapse/issues/9975))
82 - Split multipart email sending into a dedicated handler. ([\#9977](https://github.com/matrix-org/synapse/issues/9977))
83 - Run `black` on files in the `scripts` directory. ([\#9981](https://github.com/matrix-org/synapse/issues/9981))
84 - Add missing type hints to `synapse.util` module. ([\#9982](https://github.com/matrix-org/synapse/issues/9982))
85 - Simplify a few helper functions. ([\#9984](https://github.com/matrix-org/synapse/issues/9984), [\#9985](https://github.com/matrix-org/synapse/issues/9985), [\#9986](https://github.com/matrix-org/synapse/issues/9986))
86 - Remove unnecessary property from SQLBaseStore. ([\#9987](https://github.com/matrix-org/synapse/issues/9987))
87 - Remove `keylen` param on `LruCache`. ([\#9993](https://github.com/matrix-org/synapse/issues/9993))
88 - Update the Grafana dashboard in `contrib/`. ([\#10001](https://github.com/matrix-org/synapse/issues/10001))
89 - Add a batching queue implementation. ([\#10017](https://github.com/matrix-org/synapse/issues/10017))
90 - Reduce memory usage when verifying signatures on large numbers of events at once. ([\#10018](https://github.com/matrix-org/synapse/issues/10018))
91 - Properly invalidate caches for destination retry timings every (instead of expiring entries every 5 minutes). ([\#10036](https://github.com/matrix-org/synapse/issues/10036))
92 - Fix running complement tests with Synapse workers. ([\#10039](https://github.com/matrix-org/synapse/issues/10039))
93 - Fix typo in `get_state_ids_for_event` docstring where the return type was incorrect. ([\#10050](https://github.com/matrix-org/synapse/issues/10050))
94
95
096 Synapse 1.34.0 (2021-05-17)
197 ===========================
298
1313 "type": "grafana",
1414 "id": "grafana",
1515 "name": "Grafana",
16 "version": "6.7.4"
16 "version": "7.3.7"
1717 },
1818 {
1919 "type": "panel",
3737 "annotations": {
3838 "list": [
3939 {
40 "$$hashKey": "object:76",
4140 "builtIn": 1,
4241 "datasource": "$datasource",
4342 "enable": false,
5453 "gnetId": null,
5554 "graphTooltip": 0,
5655 "id": null,
57 "iteration": 1594646317221,
56 "iteration": 1621258266004,
5857 "links": [
5958 {
60 "asDropdown": true,
59 "asDropdown": false,
6160 "icon": "external link",
61 "includeVars": true,
6262 "keepTime": true,
6363 "tags": [
6464 "matrix"
8383 "type": "row"
8484 },
8585 {
86 "aliasColors": {},
87 "bars": false,
88 "dashLength": 10,
89 "dashes": false,
86 "cards": {
87 "cardPadding": -1,
88 "cardRound": 0
89 },
90 "color": {
91 "cardColor": "#b4ff00",
92 "colorScale": "sqrt",
93 "colorScheme": "interpolateInferno",
94 "exponent": 0.5,
95 "mode": "spectrum"
96 },
97 "dataFormat": "tsbuckets",
9098 "datasource": "$datasource",
91 "fill": 1,
92 "fillGradient": 0,
99 "fieldConfig": {
100 "defaults": {
101 "custom": {}
102 },
103 "overrides": []
104 },
93105 "gridPos": {
94106 "h": 9,
95107 "w": 12,
96108 "x": 0,
97109 "y": 1
98110 },
99 "hiddenSeries": false,
100 "id": 75,
101 "legend": {
102 "avg": false,
103 "current": false,
104 "max": false,
105 "min": false,
106 "show": true,
107 "total": false,
108 "values": false
109 },
110 "lines": true,
111 "linewidth": 1,
112 "links": [],
113 "nullPointMode": "null",
114 "options": {
115 "dataLinks": []
116 },
117 "paceLength": 10,
118 "percentage": false,
119 "pointradius": 5,
120 "points": false,
121 "renderer": "flot",
122 "seriesOverrides": [],
123 "spaceLength": 10,
124 "stack": false,
125 "steppedLine": false,
126 "targets": [
127 {
128 "expr": "rate(process_cpu_seconds_total{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
129 "format": "time_series",
130 "intervalFactor": 1,
131 "legendFormat": "{{job}}-{{index}} ",
132 "refId": "A"
133 }
134 ],
135 "thresholds": [
136 {
137 "colorMode": "critical",
138 "fill": true,
139 "line": true,
140 "op": "gt",
141 "value": 1,
142 "yaxis": "left"
143 }
144 ],
145 "timeFrom": null,
146 "timeRegions": [],
147 "timeShift": null,
148 "title": "CPU usage",
149 "tooltip": {
150 "shared": true,
151 "sort": 0,
152 "value_type": "individual"
153 },
154 "type": "graph",
155 "xaxis": {
156 "buckets": null,
157 "mode": "time",
158 "name": null,
159 "show": true,
160 "values": []
161 },
162 "yaxes": [
163 {
164 "decimals": null,
165 "format": "percentunit",
166 "label": null,
167 "logBase": 1,
168 "max": "1.5",
169 "min": "0",
170 "show": true
171 },
172 {
173 "format": "short",
174 "label": null,
175 "logBase": 1,
176 "max": null,
177 "min": null,
178 "show": true
179 }
180 ],
181 "yaxis": {
182 "align": false,
183 "alignLevel": null
184 }
185 },
186 {
187 "aliasColors": {},
188 "bars": false,
189 "dashLength": 10,
190 "dashes": false,
191 "datasource": "$datasource",
192 "editable": true,
193 "error": false,
194 "fill": 1,
195 "fillGradient": 0,
196 "grid": {},
197 "gridPos": {
198 "h": 9,
199 "w": 12,
200 "x": 12,
201 "y": 1
202 },
203 "hiddenSeries": false,
204 "id": 33,
205 "legend": {
206 "avg": false,
207 "current": false,
208 "max": false,
209 "min": false,
210 "show": false,
211 "total": false,
212 "values": false
213 },
214 "lines": true,
215 "linewidth": 2,
216 "links": [],
217 "nullPointMode": "null",
218 "options": {
219 "dataLinks": []
220 },
221 "paceLength": 10,
222 "percentage": false,
223 "pointradius": 5,
224 "points": false,
225 "renderer": "flot",
226 "seriesOverrides": [],
227 "spaceLength": 10,
228 "stack": false,
229 "steppedLine": false,
230 "targets": [
231 {
232 "expr": "sum(rate(synapse_storage_events_persisted_events{instance=\"$instance\"}[$bucket_size])) without (job,index)",
233 "format": "time_series",
234 "intervalFactor": 2,
235 "legendFormat": "",
236 "refId": "A",
237 "step": 20,
238 "target": ""
239 }
240 ],
241 "thresholds": [],
242 "timeFrom": null,
243 "timeRegions": [],
244 "timeShift": null,
245 "title": "Events Persisted",
246 "tooltip": {
247 "shared": true,
248 "sort": 0,
249 "value_type": "cumulative"
250 },
251 "type": "graph",
252 "xaxis": {
253 "buckets": null,
254 "mode": "time",
255 "name": null,
256 "show": true,
257 "values": []
258 },
259 "yaxes": [
260 {
261 "format": "hertz",
262 "logBase": 1,
263 "max": null,
264 "min": null,
265 "show": true
266 },
267 {
268 "format": "short",
269 "logBase": 1,
270 "max": null,
271 "min": null,
272 "show": true
273 }
274 ],
275 "yaxis": {
276 "align": false,
277 "alignLevel": null
278 }
279 },
280 {
281 "cards": {
282 "cardPadding": 0,
283 "cardRound": null
284 },
285 "color": {
286 "cardColor": "#b4ff00",
287 "colorScale": "sqrt",
288 "colorScheme": "interpolateSpectral",
289 "exponent": 0.5,
290 "mode": "spectrum"
291 },
292 "dataFormat": "tsbuckets",
293 "datasource": "$datasource",
294 "gridPos": {
295 "h": 9,
296 "w": 12,
297 "x": 0,
298 "y": 10
299 },
300111 "heatmap": {},
301 "hideZeroBuckets": true,
112 "hideZeroBuckets": false,
302113 "highlightCards": true,
303 "id": 85,
114 "id": 189,
304115 "legend": {
305116 "show": false
306117 },
308119 "reverseYBuckets": false,
309120 "targets": [
310121 {
311 "expr": "sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\"}[$bucket_size])) by (le)",
122 "expr": "sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\"}[$bucket_size])) by (le)",
312123 "format": "heatmap",
124 "interval": "",
313125 "intervalFactor": 1,
314126 "legendFormat": "{{le}}",
315127 "refId": "A"
316128 }
317129 ],
318 "title": "Event Send Time",
130 "title": "Event Send Time (excluding errors, all workers)",
319131 "tooltip": {
320132 "show": true,
321 "showHistogram": false
133 "showHistogram": true
322134 },
323135 "type": "heatmap",
324136 "xAxis": {
345157 "dashLength": 10,
346158 "dashes": false,
347159 "datasource": "$datasource",
160 "description": "",
161 "fieldConfig": {
162 "defaults": {
163 "custom": {},
164 "links": []
165 },
166 "overrides": []
167 },
348168 "fill": 0,
349169 "fillGradient": 0,
350170 "gridPos": {
351171 "h": 9,
352172 "w": 12,
353173 "x": 12,
174 "y": 1
175 },
176 "hiddenSeries": false,
177 "id": 152,
178 "legend": {
179 "avg": false,
180 "current": false,
181 "max": false,
182 "min": false,
183 "rightSide": false,
184 "show": true,
185 "total": false,
186 "values": false
187 },
188 "lines": true,
189 "linewidth": 0,
190 "links": [],
191 "nullPointMode": "connected",
192 "options": {
193 "alertThreshold": true
194 },
195 "paceLength": 10,
196 "percentage": false,
197 "pluginVersion": "7.3.7",
198 "pointradius": 5,
199 "points": false,
200 "renderer": "flot",
201 "seriesOverrides": [
202 {
203 "alias": "Avg",
204 "fill": 0,
205 "linewidth": 3
206 },
207 {
208 "alias": "99%",
209 "color": "#C4162A",
210 "fillBelowTo": "90%"
211 },
212 {
213 "alias": "90%",
214 "color": "#FF7383",
215 "fillBelowTo": "75%"
216 },
217 {
218 "alias": "75%",
219 "color": "#FFEE52",
220 "fillBelowTo": "50%"
221 },
222 {
223 "alias": "50%",
224 "color": "#73BF69",
225 "fillBelowTo": "25%"
226 },
227 {
228 "alias": "25%",
229 "color": "#1F60C4",
230 "fillBelowTo": "5%"
231 },
232 {
233 "alias": "5%",
234 "lines": false
235 },
236 {
237 "alias": "Average",
238 "color": "rgb(255, 255, 255)",
239 "lines": true,
240 "linewidth": 3
241 },
242 {
243 "alias": "Events",
244 "color": "#B877D9",
245 "hideTooltip": true,
246 "points": true,
247 "yaxis": 2,
248 "zindex": -3
249 }
250 ],
251 "spaceLength": 10,
252 "stack": false,
253 "steppedLine": false,
254 "targets": [
255 {
256 "expr": "histogram_quantile(0.99, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',index=~\"$index\",instance=\"$instance\",code=~\"2..\"}[$bucket_size])) by (le))",
257 "format": "time_series",
258 "intervalFactor": 1,
259 "legendFormat": "99%",
260 "refId": "D"
261 },
262 {
263 "expr": "histogram_quantile(0.9, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',index=~\"$index\",instance=\"$instance\",code=~\"2..\"}[$bucket_size])) by (le))",
264 "format": "time_series",
265 "interval": "",
266 "intervalFactor": 1,
267 "legendFormat": "90%",
268 "refId": "A"
269 },
270 {
271 "expr": "histogram_quantile(0.75, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',index=~\"$index\",instance=\"$instance\",code=~\"2..\"}[$bucket_size])) by (le))",
272 "format": "time_series",
273 "intervalFactor": 1,
274 "legendFormat": "75%",
275 "refId": "C"
276 },
277 {
278 "expr": "histogram_quantile(0.5, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',index=~\"$index\",instance=\"$instance\",code=~\"2..\"}[$bucket_size])) by (le))",
279 "format": "time_series",
280 "intervalFactor": 1,
281 "legendFormat": "50%",
282 "refId": "B"
283 },
284 {
285 "expr": "histogram_quantile(0.25, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',index=~\"$index\",instance=\"$instance\",code=~\"2..\"}[$bucket_size])) by (le))",
286 "legendFormat": "25%",
287 "refId": "F"
288 },
289 {
290 "expr": "histogram_quantile(0.05, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',index=~\"$index\",instance=\"$instance\",code=~\"2..\"}[$bucket_size])) by (le))",
291 "legendFormat": "5%",
292 "refId": "G"
293 },
294 {
295 "expr": "sum(rate(synapse_http_server_response_time_seconds_sum{servlet='RoomSendEventRestServlet',index=~\"$index\",instance=\"$instance\",code=~\"2..\"}[$bucket_size])) / sum(rate(synapse_http_server_response_time_seconds_count{servlet='RoomSendEventRestServlet',index=~\"$index\",instance=\"$instance\",code=~\"2..\"}[$bucket_size]))",
296 "legendFormat": "Average",
297 "refId": "H"
298 },
299 {
300 "expr": "sum(rate(synapse_storage_events_persisted_events{instance=\"$instance\"}[$bucket_size]))",
301 "hide": false,
302 "instant": false,
303 "legendFormat": "Events",
304 "refId": "E"
305 }
306 ],
307 "thresholds": [
308 {
309 "$$hashKey": "object:283",
310 "colorMode": "warning",
311 "fill": false,
312 "line": true,
313 "op": "gt",
314 "value": 1,
315 "yaxis": "left"
316 },
317 {
318 "$$hashKey": "object:284",
319 "colorMode": "critical",
320 "fill": false,
321 "line": true,
322 "op": "gt",
323 "value": 2,
324 "yaxis": "left"
325 }
326 ],
327 "timeFrom": null,
328 "timeRegions": [],
329 "timeShift": null,
330 "title": "Event Send Time Quantiles (excluding errors, all workers)",
331 "tooltip": {
332 "shared": true,
333 "sort": 2,
334 "value_type": "individual"
335 },
336 "type": "graph",
337 "xaxis": {
338 "buckets": null,
339 "mode": "time",
340 "name": null,
341 "show": true,
342 "values": []
343 },
344 "yaxes": [
345 {
346 "$$hashKey": "object:255",
347 "decimals": null,
348 "format": "s",
349 "label": "",
350 "logBase": 1,
351 "max": null,
352 "min": "0",
353 "show": true
354 },
355 {
356 "$$hashKey": "object:256",
357 "format": "hertz",
358 "label": "",
359 "logBase": 1,
360 "max": null,
361 "min": "0",
362 "show": true
363 }
364 ],
365 "yaxis": {
366 "align": false,
367 "alignLevel": null
368 }
369 },
370 {
371 "aliasColors": {},
372 "bars": false,
373 "dashLength": 10,
374 "dashes": false,
375 "datasource": "$datasource",
376 "fieldConfig": {
377 "defaults": {
378 "custom": {},
379 "links": []
380 },
381 "overrides": []
382 },
383 "fill": 1,
384 "fillGradient": 0,
385 "gridPos": {
386 "h": 9,
387 "w": 12,
388 "x": 0,
354389 "y": 10
355390 },
356391 "hiddenSeries": false,
357 "id": 107,
392 "id": 75,
358393 "legend": {
359394 "avg": false,
360395 "current": false,
365400 "values": false
366401 },
367402 "lines": true,
368 "linewidth": 1,
403 "linewidth": 3,
369404 "links": [],
370405 "nullPointMode": "null",
371406 "options": {
372 "dataLinks": []
407 "alertThreshold": true
373408 },
374409 "paceLength": 10,
375410 "percentage": false,
411 "pluginVersion": "7.3.7",
376412 "pointradius": 5,
377413 "points": false,
378414 "renderer": "flot",
379 "repeat": null,
380 "repeatDirection": "h",
381 "seriesOverrides": [
382 {
383 "alias": "mean",
384 "linewidth": 2
385 }
386 ],
415 "seriesOverrides": [],
387416 "spaceLength": 10,
388417 "stack": false,
389418 "steppedLine": false,
390419 "targets": [
391420 {
392 "expr": "histogram_quantile(0.99, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\"}[$bucket_size])) without (job, index, method))",
421 "expr": "rate(process_cpu_seconds_total{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
393422 "format": "time_series",
394423 "interval": "",
395424 "intervalFactor": 1,
396 "legendFormat": "99%",
425 "legendFormat": "{{job}}-{{index}} ",
397426 "refId": "A"
398 },
399 {
400 "expr": "histogram_quantile(0.95, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\"}[$bucket_size])) without (job, index, method))",
401 "format": "time_series",
402 "intervalFactor": 1,
403 "legendFormat": "95%",
404 "refId": "B"
405 },
406 {
407 "expr": "histogram_quantile(0.90, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\"}[$bucket_size])) without (job, index, method))",
408 "format": "time_series",
409 "intervalFactor": 1,
410 "legendFormat": "90%",
411 "refId": "C"
412 },
413 {
414 "expr": "histogram_quantile(0.50, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\"}[$bucket_size])) without (job, index, method))",
415 "format": "time_series",
416 "intervalFactor": 1,
417 "legendFormat": "50%",
418 "refId": "D"
419 },
420 {
421 "expr": "sum(rate(synapse_http_server_response_time_seconds_sum{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\"}[$bucket_size])) without (job, index, method) / sum(rate(synapse_http_server_response_time_seconds_count{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\"}[$bucket_size])) without (job, index, method)",
422 "format": "time_series",
423 "intervalFactor": 1,
424 "legendFormat": "mean",
425 "refId": "E"
426427 }
427428 ],
428 "thresholds": [],
429 "thresholds": [
430 {
431 "$$hashKey": "object:566",
432 "colorMode": "critical",
433 "fill": true,
434 "line": true,
435 "op": "gt",
436 "value": 1,
437 "yaxis": "left"
438 }
439 ],
429440 "timeFrom": null,
430441 "timeRegions": [],
431442 "timeShift": null,
432 "title": "Event send time quantiles",
443 "title": "CPU usage",
433444 "tooltip": {
434 "shared": true,
445 "shared": false,
435446 "sort": 0,
436447 "value_type": "individual"
437448 },
445456 },
446457 "yaxes": [
447458 {
448 "format": "s",
459 "$$hashKey": "object:538",
460 "decimals": null,
461 "format": "percentunit",
449462 "label": null,
450463 "logBase": 1,
451 "max": null,
452 "min": null,
464 "max": "1.5",
465 "min": "0",
453466 "show": true
454467 },
455468 {
469 "$$hashKey": "object:539",
456470 "format": "short",
457471 "label": null,
458472 "logBase": 1,
472486 "dashLength": 10,
473487 "dashes": false,
474488 "datasource": "$datasource",
475 "fill": 0,
489 "editable": true,
490 "error": false,
491 "fieldConfig": {
492 "defaults": {
493 "custom": {},
494 "links": []
495 },
496 "overrides": []
497 },
498 "fill": 1,
476499 "fillGradient": 0,
500 "grid": {},
477501 "gridPos": {
478502 "h": 9,
479503 "w": 12,
480 "x": 0,
504 "x": 12,
505 "y": 10
506 },
507 "hiddenSeries": false,
508 "id": 198,
509 "legend": {
510 "avg": false,
511 "current": false,
512 "max": false,
513 "min": false,
514 "show": true,
515 "total": false,
516 "values": false
517 },
518 "lines": true,
519 "linewidth": 3,
520 "links": [],
521 "nullPointMode": "null",
522 "options": {
523 "alertThreshold": true
524 },
525 "paceLength": 10,
526 "percentage": false,
527 "pluginVersion": "7.3.7",
528 "pointradius": 5,
529 "points": false,
530 "renderer": "flot",
531 "seriesOverrides": [],
532 "spaceLength": 10,
533 "stack": false,
534 "steppedLine": false,
535 "targets": [
536 {
537 "expr": "process_resident_memory_bytes{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
538 "format": "time_series",
539 "interval": "",
540 "intervalFactor": 2,
541 "legendFormat": "{{job}} {{index}}",
542 "refId": "A",
543 "step": 20,
544 "target": ""
545 },
546 {
547 "expr": "sum(process_resident_memory_bytes{instance=\"$instance\",job=~\"$job\",index=~\"$index\"})",
548 "hide": true,
549 "interval": "",
550 "legendFormat": "total",
551 "refId": "B"
552 }
553 ],
554 "thresholds": [],
555 "timeFrom": null,
556 "timeRegions": [],
557 "timeShift": null,
558 "title": "Memory",
559 "tooltip": {
560 "shared": false,
561 "sort": 0,
562 "value_type": "cumulative"
563 },
564 "transformations": [],
565 "type": "graph",
566 "xaxis": {
567 "buckets": null,
568 "mode": "time",
569 "name": null,
570 "show": true,
571 "values": []
572 },
573 "yaxes": [
574 {
575 "$$hashKey": "object:1560",
576 "format": "bytes",
577 "logBase": 1,
578 "max": null,
579 "min": "0",
580 "show": true
581 },
582 {
583 "$$hashKey": "object:1561",
584 "format": "short",
585 "logBase": 1,
586 "max": null,
587 "min": null,
588 "show": true
589 }
590 ],
591 "yaxis": {
592 "align": false,
593 "alignLevel": null
594 }
595 },
596 {
597 "aliasColors": {},
598 "bars": false,
599 "dashLength": 10,
600 "dashes": false,
601 "datasource": "$datasource",
602 "fieldConfig": {
603 "defaults": {
604 "custom": {},
605 "links": []
606 },
607 "overrides": []
608 },
609 "fill": 1,
610 "fillGradient": 0,
611 "gridPos": {
612 "h": 7,
613 "w": 12,
614 "x": 12,
481615 "y": 19
482616 },
483617 "hiddenSeries": false,
484 "id": 118,
618 "id": 37,
485619 "legend": {
486620 "avg": false,
487621 "current": false,
496630 "links": [],
497631 "nullPointMode": "null",
498632 "options": {
499 "dataLinks": []
633 "alertThreshold": true
500634 },
501635 "paceLength": 10,
502636 "percentage": false,
637 "pluginVersion": "7.3.7",
503638 "pointradius": 5,
504639 "points": false,
505640 "renderer": "flot",
506 "repeatDirection": "h",
507641 "seriesOverrides": [
508642 {
509 "alias": "mean",
510 "linewidth": 2
643 "$$hashKey": "object:639",
644 "alias": "/max$/",
645 "color": "#890F02",
646 "fill": 0,
647 "legend": false
511648 }
512649 ],
513650 "spaceLength": 10,
515652 "steppedLine": false,
516653 "targets": [
517654 {
518 "expr": "histogram_quantile(0.99, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method))",
655 "expr": "process_open_fds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
519656 "format": "time_series",
657 "hide": false,
520658 "interval": "",
521 "intervalFactor": 1,
522 "legendFormat": "{{job}}-{{index}} 99%",
523 "refId": "A"
659 "intervalFactor": 2,
660 "legendFormat": "{{job}}-{{index}}",
661 "refId": "A",
662 "step": 20
524663 },
525664 {
526 "expr": "histogram_quantile(0.95, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method))",
665 "expr": "process_max_fds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
527666 "format": "time_series",
528 "intervalFactor": 1,
529 "legendFormat": "{{job}}-{{index}} 95%",
530 "refId": "B"
531 },
532 {
533 "expr": "histogram_quantile(0.90, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method))",
534 "format": "time_series",
535 "intervalFactor": 1,
536 "legendFormat": "{{job}}-{{index}} 90%",
537 "refId": "C"
538 },
539 {
540 "expr": "histogram_quantile(0.50, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method))",
541 "format": "time_series",
542 "intervalFactor": 1,
543 "legendFormat": "{{job}}-{{index}} 50%",
544 "refId": "D"
545 },
546 {
547 "expr": "sum(rate(synapse_http_server_response_time_seconds_sum{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method) / sum(rate(synapse_http_server_response_time_seconds_count{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method)",
548 "format": "time_series",
549 "intervalFactor": 1,
550 "legendFormat": "{{job}}-{{index}} mean",
551 "refId": "E"
667 "hide": true,
668 "interval": "",
669 "intervalFactor": 2,
670 "legendFormat": "{{job}}-{{index}} max",
671 "refId": "B",
672 "step": 20
552673 }
553674 ],
554675 "thresholds": [],
555676 "timeFrom": null,
556677 "timeRegions": [],
557678 "timeShift": null,
558 "title": "Event send time quantiles by worker",
679 "title": "Open FDs",
559680 "tooltip": {
560 "shared": true,
681 "shared": false,
561682 "sort": 0,
562683 "value_type": "individual"
563684 },
571692 },
572693 "yaxes": [
573694 {
574 "format": "s",
575 "label": null,
695 "$$hashKey": "object:650",
696 "decimals": null,
697 "format": "none",
698 "label": "",
576699 "logBase": 1,
577700 "max": null,
578701 "min": null,
579702 "show": true
580703 },
581704 {
705 "$$hashKey": "object:651",
706 "decimals": null,
582707 "format": "short",
583708 "label": null,
584709 "logBase": 1,
599724 "h": 1,
600725 "w": 24,
601726 "x": 0,
602 "y": 28
727 "y": 26
603728 },
604729 "id": 54,
605730 "panels": [
611736 "datasource": "$datasource",
612737 "editable": true,
613738 "error": false,
739 "fieldConfig": {
740 "defaults": {
741 "custom": {},
742 "links": []
743 },
744 "overrides": []
745 },
614746 "fill": 1,
615747 "fillGradient": 0,
616748 "grid": {},
618750 "h": 7,
619751 "w": 12,
620752 "x": 0,
621 "y": 2
753 "y": 25
622754 },
623755 "hiddenSeries": false,
624756 "id": 5,
636768 "values": false
637769 },
638770 "lines": true,
639 "linewidth": 1,
771 "linewidth": 3,
640772 "links": [],
641773 "nullPointMode": "null",
642774 "options": {
643 "dataLinks": []
775 "alertThreshold": true
644776 },
645777 "paceLength": 10,
646778 "percentage": false,
779 "pluginVersion": "7.3.7",
647780 "pointradius": 5,
648781 "points": false,
649782 "renderer": "flot",
650783 "seriesOverrides": [
651784 {
785 "$$hashKey": "object:1240",
652786 "alias": "/user/"
653787 },
654788 {
789 "$$hashKey": "object:1241",
655790 "alias": "/system/"
656791 }
657792 ],
681816 ],
682817 "thresholds": [
683818 {
819 "$$hashKey": "object:1278",
684820 "colorMode": "custom",
685821 "fillColor": "rgba(255, 255, 255, 1)",
686822 "line": true,
687823 "lineColor": "rgba(216, 200, 27, 0.27)",
688824 "op": "gt",
689 "value": 0.5
690 },
691 {
825 "value": 0.5,
826 "yaxis": "left"
827 },
828 {
829 "$$hashKey": "object:1279",
692830 "colorMode": "custom",
693831 "fillColor": "rgba(255, 255, 255, 1)",
694832 "line": true,
695 "lineColor": "rgba(234, 112, 112, 0.22)",
833 "lineColor": "rgb(87, 6, 16)",
696834 "op": "gt",
697 "value": 0.8
835 "value": 0.8,
836 "yaxis": "left"
837 },
838 {
839 "$$hashKey": "object:1498",
840 "colorMode": "critical",
841 "fill": true,
842 "line": true,
843 "op": "gt",
844 "value": 1,
845 "yaxis": "left"
698846 }
699847 ],
700848 "timeFrom": null,
702850 "timeShift": null,
703851 "title": "CPU",
704852 "tooltip": {
705 "shared": true,
853 "shared": false,
706854 "sort": 0,
707855 "value_type": "individual"
708856 },
716864 },
717865 "yaxes": [
718866 {
867 "$$hashKey": "object:1250",
719868 "decimals": null,
720869 "format": "percentunit",
721870 "label": "",
725874 "show": true
726875 },
727876 {
877 "$$hashKey": "object:1251",
728878 "format": "short",
729879 "logBase": 1,
730880 "max": null,
743893 "dashLength": 10,
744894 "dashes": false,
745895 "datasource": "$datasource",
896 "description": "Shows the time in which the given percentage of reactor ticks completed, over the sampled timespan",
897 "fieldConfig": {
898 "defaults": {
899 "custom": {},
900 "links": []
901 },
902 "overrides": []
903 },
746904 "fill": 1,
747905 "fillGradient": 0,
748906 "gridPos": {
749907 "h": 7,
750908 "w": 12,
751909 "x": 12,
752 "y": 2
753 },
754 "hiddenSeries": false,
755 "id": 37,
756 "legend": {
757 "avg": false,
758 "current": false,
759 "max": false,
760 "min": false,
761 "show": true,
762 "total": false,
763 "values": false
764 },
765 "lines": true,
766 "linewidth": 1,
767 "links": [],
768 "nullPointMode": "null",
769 "options": {
770 "dataLinks": []
771 },
772 "paceLength": 10,
773 "percentage": false,
774 "pointradius": 5,
775 "points": false,
776 "renderer": "flot",
777 "seriesOverrides": [
778 {
779 "alias": "/max$/",
780 "color": "#890F02",
781 "fill": 0,
782 "legend": false
783 }
784 ],
785 "spaceLength": 10,
786 "stack": false,
787 "steppedLine": false,
788 "targets": [
789 {
790 "expr": "process_open_fds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
791 "format": "time_series",
792 "hide": false,
793 "intervalFactor": 2,
794 "legendFormat": "{{job}}-{{index}}",
795 "refId": "A",
796 "step": 20
797 },
798 {
799 "expr": "process_max_fds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
800 "format": "time_series",
801 "hide": true,
802 "intervalFactor": 2,
803 "legendFormat": "{{job}}-{{index}} max",
804 "refId": "B",
805 "step": 20
806 }
807 ],
808 "thresholds": [],
809 "timeFrom": null,
810 "timeRegions": [],
811 "timeShift": null,
812 "title": "Open FDs",
813 "tooltip": {
814 "shared": true,
815 "sort": 0,
816 "value_type": "individual"
817 },
818 "type": "graph",
819 "xaxis": {
820 "buckets": null,
821 "mode": "time",
822 "name": null,
823 "show": true,
824 "values": []
825 },
826 "yaxes": [
827 {
828 "format": "none",
829 "label": null,
830 "logBase": 1,
831 "max": null,
832 "min": null,
833 "show": true
834 },
835 {
836 "format": "short",
837 "label": null,
838 "logBase": 1,
839 "max": null,
840 "min": null,
841 "show": true
842 }
843 ],
844 "yaxis": {
845 "align": false,
846 "alignLevel": null
847 }
848 },
849 {
850 "aliasColors": {},
851 "bars": false,
852 "dashLength": 10,
853 "dashes": false,
854 "datasource": "$datasource",
855 "editable": true,
856 "error": false,
857 "fill": 0,
858 "fillGradient": 0,
859 "grid": {},
860 "gridPos": {
861 "h": 7,
862 "w": 12,
863 "x": 0,
864 "y": 9
865 },
866 "hiddenSeries": false,
867 "id": 34,
868 "legend": {
869 "avg": false,
870 "current": false,
871 "max": false,
872 "min": false,
873 "show": true,
874 "total": false,
875 "values": false
876 },
877 "lines": true,
878 "linewidth": 1,
879 "links": [],
880 "nullPointMode": "null",
881 "options": {
882 "dataLinks": []
883 },
884 "paceLength": 10,
885 "percentage": false,
886 "pointradius": 5,
887 "points": false,
888 "renderer": "flot",
889 "seriesOverrides": [],
890 "spaceLength": 10,
891 "stack": false,
892 "steppedLine": false,
893 "targets": [
894 {
895 "expr": "process_resident_memory_bytes{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
896 "format": "time_series",
897 "intervalFactor": 2,
898 "legendFormat": "{{job}} {{index}}",
899 "refId": "A",
900 "step": 20,
901 "target": ""
902 }
903 ],
904 "thresholds": [],
905 "timeFrom": null,
906 "timeRegions": [],
907 "timeShift": null,
908 "title": "Memory",
909 "tooltip": {
910 "shared": true,
911 "sort": 0,
912 "value_type": "cumulative"
913 },
914 "type": "graph",
915 "xaxis": {
916 "buckets": null,
917 "mode": "time",
918 "name": null,
919 "show": true,
920 "values": []
921 },
922 "yaxes": [
923 {
924 "format": "bytes",
925 "logBase": 1,
926 "max": null,
927 "min": "0",
928 "show": true
929 },
930 {
931 "format": "short",
932 "logBase": 1,
933 "max": null,
934 "min": null,
935 "show": true
936 }
937 ],
938 "yaxis": {
939 "align": false,
940 "alignLevel": null
941 }
942 },
943 {
944 "aliasColors": {},
945 "bars": false,
946 "dashLength": 10,
947 "dashes": false,
948 "datasource": "$datasource",
949 "description": "Shows the time in which the given percentage of reactor ticks completed, over the sampled timespan",
950 "fill": 1,
951 "fillGradient": 0,
952 "gridPos": {
953 "h": 7,
954 "w": 12,
955 "x": 12,
956 "y": 9
910 "y": 25
957911 },
958912 "hiddenSeries": false,
959913 "id": 105,
972926 "links": [],
973927 "nullPointMode": "null",
974928 "options": {
975 "dataLinks": []
929 "alertThreshold": true
976930 },
977931 "paceLength": 10,
978932 "percentage": false,
933 "pluginVersion": "7.3.7",
979934 "pointradius": 5,
980935 "points": false,
981936 "renderer": "flot",
10621017 "dashLength": 10,
10631018 "dashes": false,
10641019 "datasource": "$datasource",
1020 "editable": true,
1021 "error": false,
1022 "fieldConfig": {
1023 "defaults": {
1024 "custom": {},
1025 "links": []
1026 },
1027 "overrides": []
1028 },
10651029 "fill": 0,
10661030 "fillGradient": 0,
1031 "grid": {},
10671032 "gridPos": {
10681033 "h": 7,
10691034 "w": 12,
10701035 "x": 0,
1071 "y": 16
1036 "y": 32
10721037 },
10731038 "hiddenSeries": false,
1074 "id": 53,
1039 "id": 34,
10751040 "legend": {
10761041 "avg": false,
10771042 "current": false,
10861051 "links": [],
10871052 "nullPointMode": "null",
10881053 "options": {
1089 "dataLinks": []
1054 "alertThreshold": true
10901055 },
10911056 "paceLength": 10,
10921057 "percentage": false,
1058 "pluginVersion": "7.3.7",
10931059 "pointradius": 5,
10941060 "points": false,
10951061 "renderer": "flot",
10991065 "steppedLine": false,
11001066 "targets": [
11011067 {
1102 "expr": "min_over_time(up{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
1068 "expr": "process_resident_memory_bytes{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
11031069 "format": "time_series",
1070 "interval": "",
11041071 "intervalFactor": 2,
1105 "legendFormat": "{{job}}-{{index}}",
1106 "refId": "A"
1072 "legendFormat": "{{job}} {{index}}",
1073 "refId": "A",
1074 "step": 20,
1075 "target": ""
1076 },
1077 {
1078 "expr": "sum(process_resident_memory_bytes{instance=\"$instance\",job=~\"$job\",index=~\"$index\"})",
1079 "interval": "",
1080 "legendFormat": "total",
1081 "refId": "B"
11071082 }
11081083 ],
11091084 "thresholds": [],
11101085 "timeFrom": null,
11111086 "timeRegions": [],
11121087 "timeShift": null,
1113 "title": "Up",
1088 "title": "Memory",
11141089 "tooltip": {
1115 "shared": true,
1090 "shared": false,
11161091 "sort": 0,
1117 "value_type": "individual"
1118 },
1092 "value_type": "cumulative"
1093 },
1094 "transformations": [],
11191095 "type": "graph",
11201096 "xaxis": {
11211097 "buckets": null,
11261102 },
11271103 "yaxes": [
11281104 {
1105 "format": "bytes",
1106 "logBase": 1,
1107 "max": null,
1108 "min": "0",
1109 "show": true
1110 },
1111 {
11291112 "format": "short",
1130 "label": null,
1131 "logBase": 1,
1132 "max": null,
1133 "min": null,
1134 "show": true
1135 },
1136 {
1137 "format": "short",
1138 "label": null,
11391113 "logBase": 1,
11401114 "max": null,
11411115 "min": null,
11531127 "dashLength": 10,
11541128 "dashes": false,
11551129 "datasource": "$datasource",
1130 "fieldConfig": {
1131 "defaults": {
1132 "custom": {},
1133 "links": []
1134 },
1135 "overrides": []
1136 },
11561137 "fill": 1,
11571138 "fillGradient": 0,
11581139 "gridPos": {
11591140 "h": 7,
11601141 "w": 12,
11611142 "x": 12,
1162 "y": 16
1143 "y": 32
11631144 },
11641145 "hiddenSeries": false,
11651146 "id": 49,
11771158 "links": [],
11781159 "nullPointMode": "null",
11791160 "options": {
1180 "dataLinks": []
1161 "alertThreshold": true
11811162 },
11821163 "paceLength": 10,
11831164 "percentage": false,
1165 "pluginVersion": "7.3.7",
11841166 "pointradius": 5,
11851167 "points": false,
11861168 "renderer": "flot",
12111193 "timeShift": null,
12121194 "title": "Prometheus scrape time",
12131195 "tooltip": {
1214 "shared": true,
1196 "shared": false,
12151197 "sort": 0,
12161198 "value_type": "individual"
12171199 },
12531235 "dashLength": 10,
12541236 "dashes": false,
12551237 "datasource": "$datasource",
1238 "fieldConfig": {
1239 "defaults": {
1240 "custom": {},
1241 "links": []
1242 },
1243 "overrides": []
1244 },
1245 "fill": 0,
1246 "fillGradient": 0,
1247 "gridPos": {
1248 "h": 7,
1249 "w": 12,
1250 "x": 0,
1251 "y": 39
1252 },
1253 "hiddenSeries": false,
1254 "id": 53,
1255 "legend": {
1256 "avg": false,
1257 "current": false,
1258 "max": false,
1259 "min": false,
1260 "show": true,
1261 "total": false,
1262 "values": false
1263 },
1264 "lines": true,
1265 "linewidth": 1,
1266 "links": [],
1267 "nullPointMode": "null",
1268 "options": {
1269 "alertThreshold": true
1270 },
1271 "paceLength": 10,
1272 "percentage": false,
1273 "pluginVersion": "7.3.7",
1274 "pointradius": 5,
1275 "points": false,
1276 "renderer": "flot",
1277 "seriesOverrides": [],
1278 "spaceLength": 10,
1279 "stack": false,
1280 "steppedLine": false,
1281 "targets": [
1282 {
1283 "expr": "min_over_time(up{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
1284 "format": "time_series",
1285 "intervalFactor": 2,
1286 "legendFormat": "{{job}}-{{index}}",
1287 "refId": "A"
1288 }
1289 ],
1290 "thresholds": [],
1291 "timeFrom": null,
1292 "timeRegions": [],
1293 "timeShift": null,
1294 "title": "Up",
1295 "tooltip": {
1296 "shared": false,
1297 "sort": 0,
1298 "value_type": "individual"
1299 },
1300 "type": "graph",
1301 "xaxis": {
1302 "buckets": null,
1303 "mode": "time",
1304 "name": null,
1305 "show": true,
1306 "values": []
1307 },
1308 "yaxes": [
1309 {
1310 "format": "short",
1311 "label": null,
1312 "logBase": 1,
1313 "max": null,
1314 "min": null,
1315 "show": true
1316 },
1317 {
1318 "format": "short",
1319 "label": null,
1320 "logBase": 1,
1321 "max": null,
1322 "min": null,
1323 "show": true
1324 }
1325 ],
1326 "yaxis": {
1327 "align": false,
1328 "alignLevel": null
1329 }
1330 },
1331 {
1332 "aliasColors": {},
1333 "bars": false,
1334 "dashLength": 10,
1335 "dashes": false,
1336 "datasource": "$datasource",
1337 "fieldConfig": {
1338 "defaults": {
1339 "custom": {},
1340 "links": []
1341 },
1342 "overrides": []
1343 },
1344 "fill": 1,
1345 "fillGradient": 0,
1346 "gridPos": {
1347 "h": 7,
1348 "w": 12,
1349 "x": 12,
1350 "y": 39
1351 },
1352 "hiddenSeries": false,
1353 "id": 120,
1354 "legend": {
1355 "avg": false,
1356 "current": false,
1357 "max": false,
1358 "min": false,
1359 "show": true,
1360 "total": false,
1361 "values": false
1362 },
1363 "lines": true,
1364 "linewidth": 1,
1365 "links": [],
1366 "nullPointMode": "null as zero",
1367 "options": {
1368 "alertThreshold": true
1369 },
1370 "percentage": false,
1371 "pluginVersion": "7.3.7",
1372 "pointradius": 2,
1373 "points": false,
1374 "renderer": "flot",
1375 "seriesOverrides": [],
1376 "spaceLength": 10,
1377 "stack": true,
1378 "steppedLine": false,
1379 "targets": [
1380 {
1381 "expr": "rate(synapse_http_server_response_ru_utime_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])+rate(synapse_http_server_response_ru_stime_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
1382 "format": "time_series",
1383 "hide": false,
1384 "instant": false,
1385 "intervalFactor": 1,
1386 "legendFormat": "{{job}}-{{index}} {{method}} {{servlet}} {{tag}}",
1387 "refId": "A"
1388 },
1389 {
1390 "expr": "rate(synapse_background_process_ru_utime_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])+rate(synapse_background_process_ru_stime_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
1391 "format": "time_series",
1392 "hide": false,
1393 "instant": false,
1394 "interval": "",
1395 "intervalFactor": 1,
1396 "legendFormat": "{{job}}-{{index}} {{name}}",
1397 "refId": "B"
1398 }
1399 ],
1400 "thresholds": [
1401 {
1402 "colorMode": "critical",
1403 "fill": true,
1404 "line": true,
1405 "op": "gt",
1406 "value": 1,
1407 "yaxis": "left"
1408 }
1409 ],
1410 "timeFrom": null,
1411 "timeRegions": [],
1412 "timeShift": null,
1413 "title": "Stacked CPU usage",
1414 "tooltip": {
1415 "shared": false,
1416 "sort": 0,
1417 "value_type": "individual"
1418 },
1419 "type": "graph",
1420 "xaxis": {
1421 "buckets": null,
1422 "mode": "time",
1423 "name": null,
1424 "show": true,
1425 "values": []
1426 },
1427 "yaxes": [
1428 {
1429 "$$hashKey": "object:572",
1430 "format": "percentunit",
1431 "label": null,
1432 "logBase": 1,
1433 "max": null,
1434 "min": null,
1435 "show": true
1436 },
1437 {
1438 "$$hashKey": "object:573",
1439 "format": "short",
1440 "label": null,
1441 "logBase": 1,
1442 "max": null,
1443 "min": null,
1444 "show": true
1445 }
1446 ],
1447 "yaxis": {
1448 "align": false,
1449 "alignLevel": null
1450 }
1451 },
1452 {
1453 "aliasColors": {},
1454 "bars": false,
1455 "dashLength": 10,
1456 "dashes": false,
1457 "datasource": "$datasource",
1458 "fieldConfig": {
1459 "defaults": {
1460 "custom": {},
1461 "links": []
1462 },
1463 "overrides": []
1464 },
12561465 "fill": 1,
12571466 "fillGradient": 0,
12581467 "gridPos": {
12591468 "h": 7,
12601469 "w": 12,
12611470 "x": 0,
1262 "y": 23
1471 "y": 46
12631472 },
12641473 "hiddenSeries": false,
12651474 "id": 136,
12771486 "linewidth": 1,
12781487 "nullPointMode": "null",
12791488 "options": {
1280 "dataLinks": []
1489 "alertThreshold": true
12811490 },
12821491 "percentage": false,
1492 "pluginVersion": "7.3.7",
12831493 "pointradius": 2,
12841494 "points": false,
12851495 "renderer": "flot",
13051515 "timeShift": null,
13061516 "title": "Outgoing HTTP request rate",
13071517 "tooltip": {
1308 "shared": true,
1518 "shared": false,
13091519 "sort": 0,
13101520 "value_type": "individual"
13111521 },
13201530 "yaxes": [
13211531 {
13221532 "format": "reqps",
1323 "label": null,
1324 "logBase": 1,
1325 "max": null,
1326 "min": null,
1327 "show": true
1328 },
1329 {
1330 "format": "short",
1331 "label": null,
1332 "logBase": 1,
1333 "max": null,
1334 "min": null,
1335 "show": true
1336 }
1337 ],
1338 "yaxis": {
1339 "align": false,
1340 "alignLevel": null
1341 }
1342 },
1343 {
1344 "aliasColors": {},
1345 "bars": false,
1346 "dashLength": 10,
1347 "dashes": false,
1348 "datasource": "$datasource",
1349 "fill": 1,
1350 "fillGradient": 0,
1351 "gridPos": {
1352 "h": 7,
1353 "w": 12,
1354 "x": 12,
1355 "y": 23
1356 },
1357 "hiddenSeries": false,
1358 "id": 120,
1359 "legend": {
1360 "avg": false,
1361 "current": false,
1362 "max": false,
1363 "min": false,
1364 "show": true,
1365 "total": false,
1366 "values": false
1367 },
1368 "lines": true,
1369 "linewidth": 1,
1370 "links": [],
1371 "nullPointMode": "null",
1372 "options": {
1373 "dataLinks": []
1374 },
1375 "percentage": false,
1376 "pointradius": 2,
1377 "points": false,
1378 "renderer": "flot",
1379 "seriesOverrides": [],
1380 "spaceLength": 10,
1381 "stack": true,
1382 "steppedLine": false,
1383 "targets": [
1384 {
1385 "expr": "rate(synapse_http_server_response_ru_utime_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])+rate(synapse_http_server_response_ru_stime_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
1386 "format": "time_series",
1387 "hide": false,
1388 "instant": false,
1389 "intervalFactor": 1,
1390 "legendFormat": "{{job}}-{{index}} {{method}} {{servlet}} {{tag}}",
1391 "refId": "A"
1392 },
1393 {
1394 "expr": "rate(synapse_background_process_ru_utime_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])+rate(synapse_background_process_ru_stime_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
1395 "format": "time_series",
1396 "hide": false,
1397 "instant": false,
1398 "interval": "",
1399 "intervalFactor": 1,
1400 "legendFormat": "{{job}}-{{index}} {{name}}",
1401 "refId": "B"
1402 }
1403 ],
1404 "thresholds": [
1405 {
1406 "colorMode": "critical",
1407 "fill": true,
1408 "line": true,
1409 "op": "gt",
1410 "value": 1,
1411 "yaxis": "left"
1412 }
1413 ],
1414 "timeFrom": null,
1415 "timeRegions": [],
1416 "timeShift": null,
1417 "title": "Stacked CPU usage",
1418 "tooltip": {
1419 "shared": false,
1420 "sort": 0,
1421 "value_type": "individual"
1422 },
1423 "type": "graph",
1424 "xaxis": {
1425 "buckets": null,
1426 "mode": "time",
1427 "name": null,
1428 "show": true,
1429 "values": []
1430 },
1431 "yaxes": [
1432 {
1433 "format": "percentunit",
14341533 "label": null,
14351534 "logBase": 1,
14361535 "max": null,
14631562 "h": 1,
14641563 "w": 24,
14651564 "x": 0,
1466 "y": 29
1565 "y": 27
14671566 },
14681567 "id": 56,
14691568 "panels": [
1569 {
1570 "cards": {
1571 "cardPadding": -1,
1572 "cardRound": 0
1573 },
1574 "color": {
1575 "cardColor": "#b4ff00",
1576 "colorScale": "sqrt",
1577 "colorScheme": "interpolateInferno",
1578 "exponent": 0.5,
1579 "mode": "spectrum"
1580 },
1581 "dataFormat": "tsbuckets",
1582 "datasource": "$datasource",
1583 "fieldConfig": {
1584 "defaults": {
1585 "custom": {}
1586 },
1587 "overrides": []
1588 },
1589 "gridPos": {
1590 "h": 9,
1591 "w": 12,
1592 "x": 0,
1593 "y": 21
1594 },
1595 "heatmap": {},
1596 "hideZeroBuckets": false,
1597 "highlightCards": true,
1598 "id": 85,
1599 "legend": {
1600 "show": false
1601 },
1602 "links": [],
1603 "reverseYBuckets": false,
1604 "targets": [
1605 {
1606 "expr": "sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\"}[$bucket_size])) by (le)",
1607 "format": "heatmap",
1608 "intervalFactor": 1,
1609 "legendFormat": "{{le}}",
1610 "refId": "A"
1611 }
1612 ],
1613 "title": "Event Send Time (Including errors, across all workers)",
1614 "tooltip": {
1615 "show": true,
1616 "showHistogram": true
1617 },
1618 "type": "heatmap",
1619 "xAxis": {
1620 "show": true
1621 },
1622 "xBucketNumber": null,
1623 "xBucketSize": null,
1624 "yAxis": {
1625 "decimals": null,
1626 "format": "s",
1627 "logBase": 2,
1628 "max": null,
1629 "min": null,
1630 "show": true,
1631 "splitFactor": null
1632 },
1633 "yBucketBound": "auto",
1634 "yBucketNumber": null,
1635 "yBucketSize": null
1636 },
1637 {
1638 "aliasColors": {},
1639 "bars": false,
1640 "dashLength": 10,
1641 "dashes": false,
1642 "datasource": "$datasource",
1643 "description": "",
1644 "editable": true,
1645 "error": false,
1646 "fieldConfig": {
1647 "defaults": {
1648 "custom": {},
1649 "links": []
1650 },
1651 "overrides": []
1652 },
1653 "fill": 1,
1654 "fillGradient": 0,
1655 "grid": {},
1656 "gridPos": {
1657 "h": 9,
1658 "w": 12,
1659 "x": 12,
1660 "y": 21
1661 },
1662 "hiddenSeries": false,
1663 "id": 33,
1664 "legend": {
1665 "avg": false,
1666 "current": false,
1667 "max": false,
1668 "min": false,
1669 "show": false,
1670 "total": false,
1671 "values": false
1672 },
1673 "lines": true,
1674 "linewidth": 2,
1675 "links": [],
1676 "nullPointMode": "null",
1677 "options": {
1678 "alertThreshold": true
1679 },
1680 "paceLength": 10,
1681 "percentage": false,
1682 "pluginVersion": "7.3.7",
1683 "pointradius": 5,
1684 "points": false,
1685 "renderer": "flot",
1686 "seriesOverrides": [],
1687 "spaceLength": 10,
1688 "stack": false,
1689 "steppedLine": false,
1690 "targets": [
1691 {
1692 "expr": "sum(rate(synapse_storage_events_persisted_events{instance=\"$instance\"}[$bucket_size])) without (job,index)",
1693 "format": "time_series",
1694 "interval": "",
1695 "intervalFactor": 2,
1696 "legendFormat": "",
1697 "refId": "A",
1698 "step": 20,
1699 "target": ""
1700 }
1701 ],
1702 "thresholds": [],
1703 "timeFrom": null,
1704 "timeRegions": [],
1705 "timeShift": null,
1706 "title": "Events Persisted (all workers)",
1707 "tooltip": {
1708 "shared": true,
1709 "sort": 0,
1710 "value_type": "cumulative"
1711 },
1712 "type": "graph",
1713 "xaxis": {
1714 "buckets": null,
1715 "mode": "time",
1716 "name": null,
1717 "show": true,
1718 "values": []
1719 },
1720 "yaxes": [
1721 {
1722 "$$hashKey": "object:102",
1723 "format": "hertz",
1724 "logBase": 1,
1725 "max": null,
1726 "min": null,
1727 "show": true
1728 },
1729 {
1730 "$$hashKey": "object:103",
1731 "format": "short",
1732 "logBase": 1,
1733 "max": null,
1734 "min": null,
1735 "show": true
1736 }
1737 ],
1738 "yaxis": {
1739 "align": false,
1740 "alignLevel": null
1741 }
1742 },
14701743 {
14711744 "aliasColors": {},
14721745 "bars": false,
14741747 "dashes": false,
14751748 "datasource": "$datasource",
14761749 "decimals": 1,
1750 "fieldConfig": {
1751 "defaults": {
1752 "custom": {}
1753 },
1754 "overrides": []
1755 },
14771756 "fill": 1,
1757 "fillGradient": 0,
14781758 "gridPos": {
14791759 "h": 7,
14801760 "w": 12,
14811761 "x": 0,
1482 "y": 58
1483 },
1762 "y": 30
1763 },
1764 "hiddenSeries": false,
14841765 "id": 40,
14851766 "legend": {
14861767 "avg": false,
14951776 "linewidth": 1,
14961777 "links": [],
14971778 "nullPointMode": "null",
1779 "options": {
1780 "alertThreshold": true
1781 },
14981782 "percentage": false,
1783 "pluginVersion": "7.3.7",
14991784 "pointradius": 5,
15001785 "points": false,
15011786 "renderer": "flot",
15601845 "dashes": false,
15611846 "datasource": "$datasource",
15621847 "decimals": 1,
1848 "fieldConfig": {
1849 "defaults": {
1850 "custom": {}
1851 },
1852 "overrides": []
1853 },
15631854 "fill": 1,
1855 "fillGradient": 0,
15641856 "gridPos": {
15651857 "h": 7,
15661858 "w": 12,
15671859 "x": 12,
1568 "y": 58
1569 },
1860 "y": 30
1861 },
1862 "hiddenSeries": false,
15701863 "id": 46,
15711864 "legend": {
15721865 "avg": false,
15811874 "linewidth": 1,
15821875 "links": [],
15831876 "nullPointMode": "null",
1877 "options": {
1878 "alertThreshold": true
1879 },
15841880 "percentage": false,
1881 "pluginVersion": "7.3.7",
15851882 "pointradius": 5,
15861883 "points": false,
15871884 "renderer": "flot",
16501947 "dashes": false,
16511948 "datasource": "$datasource",
16521949 "decimals": 1,
1950 "fieldConfig": {
1951 "defaults": {
1952 "custom": {}
1953 },
1954 "overrides": []
1955 },
16531956 "fill": 1,
1957 "fillGradient": 0,
16541958 "gridPos": {
16551959 "h": 7,
16561960 "w": 12,
16571961 "x": 0,
1658 "y": 65
1659 },
1962 "y": 37
1963 },
1964 "hiddenSeries": false,
16601965 "id": 44,
16611966 "legend": {
16621967 "alignAsTable": true,
16741979 "linewidth": 1,
16751980 "links": [],
16761981 "nullPointMode": "null",
1982 "options": {
1983 "alertThreshold": true
1984 },
16771985 "percentage": false,
1986 "pluginVersion": "7.3.7",
16781987 "pointradius": 5,
16791988 "points": false,
16801989 "renderer": "flot",
17402049 "dashes": false,
17412050 "datasource": "$datasource",
17422051 "decimals": 1,
2052 "fieldConfig": {
2053 "defaults": {
2054 "custom": {}
2055 },
2056 "overrides": []
2057 },
17432058 "fill": 1,
2059 "fillGradient": 0,
17442060 "gridPos": {
17452061 "h": 7,
17462062 "w": 12,
17472063 "x": 12,
1748 "y": 65
1749 },
2064 "y": 37
2065 },
2066 "hiddenSeries": false,
17502067 "id": 45,
17512068 "legend": {
17522069 "alignAsTable": true,
17642081 "linewidth": 1,
17652082 "links": [],
17662083 "nullPointMode": "null",
2084 "options": {
2085 "alertThreshold": true
2086 },
17672087 "percentage": false,
2088 "pluginVersion": "7.3.7",
17682089 "pointradius": 5,
17692090 "points": false,
17702091 "renderer": "flot",
18222143 "align": false,
18232144 "alignLevel": null
18242145 }
2146 },
2147 {
2148 "aliasColors": {},
2149 "bars": false,
2150 "dashLength": 10,
2151 "dashes": false,
2152 "datasource": "$datasource",
2153 "fieldConfig": {
2154 "defaults": {
2155 "custom": {},
2156 "links": []
2157 },
2158 "overrides": []
2159 },
2160 "fill": 0,
2161 "fillGradient": 0,
2162 "gridPos": {
2163 "h": 9,
2164 "w": 12,
2165 "x": 0,
2166 "y": 44
2167 },
2168 "hiddenSeries": false,
2169 "id": 118,
2170 "legend": {
2171 "avg": false,
2172 "current": false,
2173 "max": false,
2174 "min": false,
2175 "show": true,
2176 "total": false,
2177 "values": false
2178 },
2179 "lines": true,
2180 "linewidth": 1,
2181 "links": [],
2182 "nullPointMode": "null",
2183 "options": {
2184 "alertThreshold": true
2185 },
2186 "paceLength": 10,
2187 "percentage": false,
2188 "pluginVersion": "7.3.7",
2189 "pointradius": 5,
2190 "points": false,
2191 "renderer": "flot",
2192 "repeatDirection": "h",
2193 "seriesOverrides": [
2194 {
2195 "alias": "mean",
2196 "linewidth": 2
2197 }
2198 ],
2199 "spaceLength": 10,
2200 "stack": false,
2201 "steppedLine": false,
2202 "targets": [
2203 {
2204 "expr": "histogram_quantile(0.99, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method))",
2205 "format": "time_series",
2206 "interval": "",
2207 "intervalFactor": 1,
2208 "legendFormat": "{{job}}-{{index}} 99%",
2209 "refId": "A"
2210 },
2211 {
2212 "expr": "histogram_quantile(0.95, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method))",
2213 "format": "time_series",
2214 "interval": "",
2215 "intervalFactor": 1,
2216 "legendFormat": "{{job}}-{{index}} 95%",
2217 "refId": "B"
2218 },
2219 {
2220 "expr": "histogram_quantile(0.90, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method))",
2221 "format": "time_series",
2222 "intervalFactor": 1,
2223 "legendFormat": "{{job}}-{{index}} 90%",
2224 "refId": "C"
2225 },
2226 {
2227 "expr": "histogram_quantile(0.50, sum(rate(synapse_http_server_response_time_seconds_bucket{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method))",
2228 "format": "time_series",
2229 "intervalFactor": 1,
2230 "legendFormat": "{{job}}-{{index}} 50%",
2231 "refId": "D"
2232 },
2233 {
2234 "expr": "sum(rate(synapse_http_server_response_time_seconds_sum{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method) / sum(rate(synapse_http_server_response_time_seconds_count{servlet='RoomSendEventRestServlet',instance=\"$instance\",code=~\"2..\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (method)",
2235 "format": "time_series",
2236 "intervalFactor": 1,
2237 "legendFormat": "{{job}}-{{index}} mean",
2238 "refId": "E"
2239 }
2240 ],
2241 "thresholds": [],
2242 "timeFrom": null,
2243 "timeRegions": [],
2244 "timeShift": null,
2245 "title": "Event send time quantiles by worker",
2246 "tooltip": {
2247 "shared": true,
2248 "sort": 0,
2249 "value_type": "individual"
2250 },
2251 "type": "graph",
2252 "xaxis": {
2253 "buckets": null,
2254 "mode": "time",
2255 "name": null,
2256 "show": true,
2257 "values": []
2258 },
2259 "yaxes": [
2260 {
2261 "format": "s",
2262 "label": null,
2263 "logBase": 1,
2264 "max": null,
2265 "min": null,
2266 "show": true
2267 },
2268 {
2269 "format": "short",
2270 "label": null,
2271 "logBase": 1,
2272 "max": null,
2273 "min": null,
2274 "show": true
2275 }
2276 ],
2277 "yaxis": {
2278 "align": false,
2279 "alignLevel": null
2280 }
18252281 }
18262282 ],
18272283 "repeat": null,
1828 "title": "Event persist rates",
2284 "title": "Event persistence",
18292285 "type": "row"
18302286 },
18312287 {
18352291 "h": 1,
18362292 "w": 24,
18372293 "x": 0,
1838 "y": 30
2294 "y": 28
18392295 },
18402296 "id": 57,
18412297 "panels": [
18482304 "decimals": null,
18492305 "editable": true,
18502306 "error": false,
2307 "fieldConfig": {
2308 "defaults": {
2309 "custom": {},
2310 "links": []
2311 },
2312 "overrides": []
2313 },
18512314 "fill": 2,
18522315 "fillGradient": 0,
18532316 "grid": {},
18772340 "links": [],
18782341 "nullPointMode": "null",
18792342 "options": {
1880 "dataLinks": []
2343 "alertThreshold": true
18812344 },
18822345 "percentage": false,
2346 "pluginVersion": "7.3.7",
18832347 "pointradius": 5,
18842348 "points": false,
18852349 "renderer": "flot",
19042368 "fill": true,
19052369 "fillColor": "rgba(216, 200, 27, 0.27)",
19062370 "op": "gt",
1907 "value": 100
2371 "value": 100,
2372 "yaxis": "left"
19082373 },
19092374 {
19102375 "colorMode": "custom",
19112376 "fill": true,
19122377 "fillColor": "rgba(234, 112, 112, 0.22)",
19132378 "op": "gt",
1914 "value": 250
2379 "value": 250,
2380 "yaxis": "left"
19152381 }
19162382 ],
19172383 "timeFrom": null,
19202386 "title": "Request Count by arrival time",
19212387 "tooltip": {
19222388 "shared": false,
1923 "sort": 0,
2389 "sort": 2,
19242390 "value_type": "individual"
19252391 },
19262392 "type": "graph",
19602426 "datasource": "$datasource",
19612427 "editable": true,
19622428 "error": false,
2429 "fieldConfig": {
2430 "defaults": {
2431 "custom": {},
2432 "links": []
2433 },
2434 "overrides": []
2435 },
19632436 "fill": 1,
19642437 "fillGradient": 0,
19652438 "grid": {},
19852458 "links": [],
19862459 "nullPointMode": "null",
19872460 "options": {
1988 "dataLinks": []
2461 "alertThreshold": true
19892462 },
19902463 "percentage": false,
2464 "pluginVersion": "7.3.7",
19912465 "pointradius": 5,
19922466 "points": false,
19932467 "renderer": "flot",
20132487 "title": "Top 10 Request Counts",
20142488 "tooltip": {
20152489 "shared": false,
2016 "sort": 0,
2490 "sort": 2,
20172491 "value_type": "cumulative"
20182492 },
20192493 "type": "graph",
20542528 "decimals": null,
20552529 "editable": true,
20562530 "error": false,
2531 "fieldConfig": {
2532 "defaults": {
2533 "custom": {},
2534 "links": []
2535 },
2536 "overrides": []
2537 },
20572538 "fill": 2,
20582539 "fillGradient": 0,
20592540 "grid": {},
20832564 "links": [],
20842565 "nullPointMode": "null",
20852566 "options": {
2086 "dataLinks": []
2567 "alertThreshold": true
20872568 },
20882569 "percentage": false,
2570 "pluginVersion": "7.3.7",
20892571 "pointradius": 5,
20902572 "points": false,
20912573 "renderer": "flot",
21282610 "title": "Total CPU Usage by Endpoint",
21292611 "tooltip": {
21302612 "shared": false,
2131 "sort": 0,
2613 "sort": 2,
21322614 "value_type": "individual"
21332615 },
21342616 "type": "graph",
21692651 "decimals": null,
21702652 "editable": true,
21712653 "error": false,
2172 "fill": 2,
2654 "fieldConfig": {
2655 "defaults": {
2656 "custom": {},
2657 "links": []
2658 },
2659 "overrides": []
2660 },
2661 "fill": 0,
21732662 "fillGradient": 0,
21742663 "grid": {},
21752664 "gridPos": {
21982687 "links": [],
21992688 "nullPointMode": "null",
22002689 "options": {
2201 "dataLinks": []
2690 "alertThreshold": true
22022691 },
22032692 "percentage": false,
2693 "pluginVersion": "7.3.7",
22042694 "pointradius": 5,
22052695 "points": false,
22062696 "renderer": "flot",
22132703 "expr": "(rate(synapse_http_server_in_flight_requests_ru_utime_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])+rate(synapse_http_server_in_flight_requests_ru_stime_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) / rate(synapse_http_server_requests_received{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
22142704 "format": "time_series",
22152705 "interval": "",
2216 "intervalFactor": 2,
2706 "intervalFactor": 1,
22172707 "legendFormat": "{{job}}-{{index}} {{method}} {{servlet}} {{tag}}",
22182708 "refId": "A",
22192709 "step": 20
22252715 "fill": true,
22262716 "fillColor": "rgba(216, 200, 27, 0.27)",
22272717 "op": "gt",
2228 "value": 100
2718 "value": 100,
2719 "yaxis": "left"
22292720 },
22302721 {
22312722 "colorMode": "custom",
22322723 "fill": true,
22332724 "fillColor": "rgba(234, 112, 112, 0.22)",
22342725 "op": "gt",
2235 "value": 250
2726 "value": 250,
2727 "yaxis": "left"
22362728 }
22372729 ],
22382730 "timeFrom": null,
22412733 "title": "Average CPU Usage by Endpoint",
22422734 "tooltip": {
22432735 "shared": false,
2244 "sort": 0,
2736 "sort": 2,
22452737 "value_type": "individual"
22462738 },
22472739 "type": "graph",
22812773 "datasource": "$datasource",
22822774 "editable": true,
22832775 "error": false,
2776 "fieldConfig": {
2777 "defaults": {
2778 "custom": {},
2779 "links": []
2780 },
2781 "overrides": []
2782 },
22842783 "fill": 1,
22852784 "fillGradient": 0,
22862785 "grid": {},
23092808 "links": [],
23102809 "nullPointMode": "null",
23112810 "options": {
2312 "dataLinks": []
2811 "alertThreshold": true
23132812 },
23142813 "percentage": false,
2814 "pluginVersion": "7.3.7",
23152815 "pointradius": 5,
23162816 "points": false,
23172817 "renderer": "flot",
23242824 "expr": "rate(synapse_http_server_in_flight_requests_db_txn_duration_seconds{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
23252825 "format": "time_series",
23262826 "interval": "",
2327 "intervalFactor": 2,
2827 "intervalFactor": 1,
23282828 "legendFormat": "{{job}}-{{index}} {{method}} {{servlet}} {{tag}}",
23292829 "refId": "A",
23302830 "step": 20
23372837 "title": "DB Usage by endpoint",
23382838 "tooltip": {
23392839 "shared": false,
2340 "sort": 0,
2840 "sort": 2,
23412841 "value_type": "cumulative"
23422842 },
23432843 "type": "graph",
23782878 "decimals": null,
23792879 "editable": true,
23802880 "error": false,
2881 "fieldConfig": {
2882 "defaults": {
2883 "custom": {},
2884 "links": []
2885 },
2886 "overrides": []
2887 },
23812888 "fill": 2,
23822889 "fillGradient": 0,
23832890 "grid": {},
24072914 "links": [],
24082915 "nullPointMode": "null",
24092916 "options": {
2410 "dataLinks": []
2917 "alertThreshold": true
24112918 },
24122919 "percentage": false,
2920 "pluginVersion": "7.3.7",
24132921 "pointradius": 5,
24142922 "points": false,
24152923 "renderer": "flot",
24232931 "format": "time_series",
24242932 "hide": false,
24252933 "interval": "",
2426 "intervalFactor": 2,
2934 "intervalFactor": 1,
24272935 "legendFormat": "{{job}}-{{index}} {{method}} {{servlet}}",
24282936 "refId": "A",
24292937 "step": 20
24362944 "title": "Non-sync avg response time",
24372945 "tooltip": {
24382946 "shared": false,
2439 "sort": 0,
2947 "sort": 2,
24402948 "value_type": "individual"
24412949 },
24422950 "type": "graph",
24742982 "dashLength": 10,
24752983 "dashes": false,
24762984 "datasource": "$datasource",
2985 "fieldConfig": {
2986 "defaults": {
2987 "custom": {},
2988 "links": []
2989 },
2990 "overrides": []
2991 },
24772992 "fill": 1,
24782993 "fillGradient": 0,
24792994 "gridPos": {
24983013 "links": [],
24993014 "nullPointMode": "null",
25003015 "options": {
2501 "dataLinks": []
3016 "alertThreshold": true
25023017 },
25033018 "percentage": false,
3019 "pluginVersion": "7.3.7",
25043020 "pointradius": 5,
25053021 "points": false,
25063022 "renderer": "flot",
2507 "seriesOverrides": [],
3023 "seriesOverrides": [
3024 {
3025 "alias": "Total",
3026 "color": "rgb(255, 255, 255)",
3027 "fill": 0,
3028 "linewidth": 3
3029 }
3030 ],
25083031 "spaceLength": 10,
25093032 "stack": false,
25103033 "steppedLine": false,
25163039 "intervalFactor": 1,
25173040 "legendFormat": "{{job}}-{{index}} {{method}} {{servlet}}",
25183041 "refId": "A"
3042 },
3043 {
3044 "expr": "sum(avg_over_time(synapse_http_server_in_flight_requests_count{job=\"$job\",index=~\"$index\",instance=\"$instance\"}[$bucket_size]))",
3045 "interval": "",
3046 "legendFormat": "Total",
3047 "refId": "B"
25193048 }
25203049 ],
25213050 "thresholds": [],
25253054 "title": "Requests in flight",
25263055 "tooltip": {
25273056 "shared": false,
2528 "sort": 0,
3057 "sort": 2,
25293058 "value_type": "individual"
25303059 },
25313060 "type": "graph",
25713100 "h": 1,
25723101 "w": 24,
25733102 "x": 0,
2574 "y": 31
3103 "y": 29
25753104 },
25763105 "id": 97,
25773106 "panels": [
25813110 "dashLength": 10,
25823111 "dashes": false,
25833112 "datasource": "$datasource",
3113 "fieldConfig": {
3114 "defaults": {
3115 "custom": {},
3116 "links": []
3117 },
3118 "overrides": []
3119 },
25843120 "fill": 1,
25853121 "fillGradient": 0,
25863122 "gridPos": {
26043140 "linewidth": 1,
26053141 "links": [],
26063142 "nullPointMode": "null",
2607 "options": {
2608 "dataLinks": []
2609 },
26103143 "paceLength": 10,
26113144 "percentage": false,
3145 "pluginVersion": "7.1.3",
26123146 "pointradius": 5,
26133147 "points": false,
26143148 "renderer": "flot",
26733207 "dashLength": 10,
26743208 "dashes": false,
26753209 "datasource": "$datasource",
3210 "fieldConfig": {
3211 "defaults": {
3212 "custom": {},
3213 "links": []
3214 },
3215 "overrides": []
3216 },
26763217 "fill": 1,
26773218 "fillGradient": 0,
26783219 "gridPos": {
26963237 "linewidth": 1,
26973238 "links": [],
26983239 "nullPointMode": "null",
2699 "options": {
2700 "dataLinks": []
2701 },
27023240 "paceLength": 10,
27033241 "percentage": false,
3242 "pluginVersion": "7.1.3",
27043243 "pointradius": 5,
27053244 "points": false,
27063245 "renderer": "flot",
27163255 "intervalFactor": 1,
27173256 "legendFormat": "{{job}}-{{index}} {{name}}",
27183257 "refId": "A"
2719 },
2720 {
2721 "expr": "",
2722 "format": "time_series",
2723 "intervalFactor": 1,
2724 "refId": "B"
27253258 }
27263259 ],
27273260 "thresholds": [],
27303263 "timeShift": null,
27313264 "title": "DB usage by background jobs (including scheduling time)",
27323265 "tooltip": {
2733 "shared": true,
3266 "shared": false,
27343267 "sort": 0,
27353268 "value_type": "individual"
27363269 },
27713304 "dashLength": 10,
27723305 "dashes": false,
27733306 "datasource": "$datasource",
3307 "fieldConfig": {
3308 "defaults": {
3309 "custom": {},
3310 "links": []
3311 },
3312 "overrides": []
3313 },
27743314 "fill": 1,
27753315 "fillGradient": 0,
27763316 "gridPos": {
27933333 "lines": true,
27943334 "linewidth": 1,
27953335 "nullPointMode": "null",
2796 "options": {
2797 "dataLinks": []
2798 },
27993336 "percentage": false,
3337 "pluginVersion": "7.1.3",
28003338 "pointradius": 2,
28013339 "points": false,
28023340 "renderer": "flot",
28633401 "h": 1,
28643402 "w": 24,
28653403 "x": 0,
2866 "y": 32
3404 "y": 30
28673405 },
28683406 "id": 81,
28693407 "panels": [
28733411 "dashLength": 10,
28743412 "dashes": false,
28753413 "datasource": "$datasource",
3414 "fieldConfig": {
3415 "defaults": {
3416 "custom": {},
3417 "links": []
3418 },
3419 "overrides": []
3420 },
28763421 "fill": 1,
28773422 "fillGradient": 0,
28783423 "gridPos": {
28793424 "h": 9,
28803425 "w": 12,
28813426 "x": 0,
2882 "y": 6
3427 "y": 33
28833428 },
28843429 "hiddenSeries": false,
28853430 "id": 79,
28963441 "linewidth": 1,
28973442 "links": [],
28983443 "nullPointMode": "null",
2899 "options": {
2900 "dataLinks": []
2901 },
29023444 "paceLength": 10,
29033445 "percentage": false,
3446 "pluginVersion": "7.1.3",
29043447 "pointradius": 5,
29053448 "points": false,
29063449 "renderer": "flot",
29693512 "dashLength": 10,
29703513 "dashes": false,
29713514 "datasource": "$datasource",
3515 "fieldConfig": {
3516 "defaults": {
3517 "custom": {},
3518 "links": []
3519 },
3520 "overrides": []
3521 },
29723522 "fill": 1,
29733523 "fillGradient": 0,
29743524 "gridPos": {
29753525 "h": 9,
29763526 "w": 12,
29773527 "x": 12,
2978 "y": 6
3528 "y": 33
29793529 },
29803530 "hiddenSeries": false,
29813531 "id": 83,
29923542 "linewidth": 1,
29933543 "links": [],
29943544 "nullPointMode": "null",
2995 "options": {
2996 "dataLinks": []
2997 },
29983545 "paceLength": 10,
29993546 "percentage": false,
3547 "pluginVersion": "7.1.3",
30003548 "pointradius": 5,
30013549 "points": false,
30023550 "renderer": "flot",
30673615 "dashLength": 10,
30683616 "dashes": false,
30693617 "datasource": "$datasource",
3618 "fieldConfig": {
3619 "defaults": {
3620 "custom": {},
3621 "links": []
3622 },
3623 "overrides": []
3624 },
30703625 "fill": 1,
30713626 "fillGradient": 0,
30723627 "gridPos": {
30733628 "h": 9,
30743629 "w": 12,
30753630 "x": 0,
3076 "y": 15
3631 "y": 42
30773632 },
30783633 "hiddenSeries": false,
30793634 "id": 109,
30903645 "linewidth": 1,
30913646 "links": [],
30923647 "nullPointMode": "null",
3093 "options": {
3094 "dataLinks": []
3095 },
30963648 "paceLength": 10,
30973649 "percentage": false,
3650 "pluginVersion": "7.1.3",
30983651 "pointradius": 5,
30993652 "points": false,
31003653 "renderer": "flot",
31663719 "dashLength": 10,
31673720 "dashes": false,
31683721 "datasource": "$datasource",
3722 "fieldConfig": {
3723 "defaults": {
3724 "custom": {},
3725 "links": []
3726 },
3727 "overrides": []
3728 },
31693729 "fill": 1,
31703730 "fillGradient": 0,
31713731 "gridPos": {
31723732 "h": 9,
31733733 "w": 12,
31743734 "x": 12,
3175 "y": 15
3735 "y": 42
31763736 },
31773737 "hiddenSeries": false,
31783738 "id": 111,
31893749 "linewidth": 1,
31903750 "links": [],
31913751 "nullPointMode": "null",
3192 "options": {
3193 "dataLinks": []
3194 },
31953752 "paceLength": 10,
31963753 "percentage": false,
3754 "pluginVersion": "7.1.3",
31973755 "pointradius": 5,
31983756 "points": false,
31993757 "renderer": "flot",
32573815 "bars": false,
32583816 "dashLength": 10,
32593817 "dashes": false,
3818 "datasource": "${DS_PROMETHEUS}",
3819 "description": "The number of events in the in-memory queues ",
3820 "fieldConfig": {
3821 "defaults": {
3822 "custom": {},
3823 "links": []
3824 },
3825 "overrides": []
3826 },
3827 "fill": 1,
3828 "fillGradient": 0,
3829 "gridPos": {
3830 "h": 8,
3831 "w": 12,
3832 "x": 0,
3833 "y": 51
3834 },
3835 "hiddenSeries": false,
3836 "id": 142,
3837 "legend": {
3838 "avg": false,
3839 "current": false,
3840 "max": false,
3841 "min": false,
3842 "show": true,
3843 "total": false,
3844 "values": false
3845 },
3846 "lines": true,
3847 "linewidth": 1,
3848 "nullPointMode": "null",
3849 "percentage": false,
3850 "pluginVersion": "7.1.3",
3851 "pointradius": 2,
3852 "points": false,
3853 "renderer": "flot",
3854 "seriesOverrides": [],
3855 "spaceLength": 10,
3856 "stack": false,
3857 "steppedLine": false,
3858 "targets": [
3859 {
3860 "expr": "synapse_federation_transaction_queue_pending_pdus{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
3861 "interval": "",
3862 "legendFormat": "pending PDUs {{job}}-{{index}}",
3863 "refId": "A"
3864 },
3865 {
3866 "expr": "synapse_federation_transaction_queue_pending_edus{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
3867 "interval": "",
3868 "legendFormat": "pending EDUs {{job}}-{{index}}",
3869 "refId": "B"
3870 }
3871 ],
3872 "thresholds": [],
3873 "timeFrom": null,
3874 "timeRegions": [],
3875 "timeShift": null,
3876 "title": "In-memory federation transmission queues",
3877 "tooltip": {
3878 "shared": true,
3879 "sort": 0,
3880 "value_type": "individual"
3881 },
3882 "type": "graph",
3883 "xaxis": {
3884 "buckets": null,
3885 "mode": "time",
3886 "name": null,
3887 "show": true,
3888 "values": []
3889 },
3890 "yaxes": [
3891 {
3892 "format": "short",
3893 "label": "events",
3894 "logBase": 1,
3895 "max": null,
3896 "min": "0",
3897 "show": true
3898 },
3899 {
3900 "format": "short",
3901 "label": "",
3902 "logBase": 1,
3903 "max": null,
3904 "min": null,
3905 "show": true
3906 }
3907 ],
3908 "yaxis": {
3909 "align": false,
3910 "alignLevel": null
3911 }
3912 },
3913 {
3914 "aliasColors": {},
3915 "bars": false,
3916 "dashLength": 10,
3917 "dashes": false,
32603918 "datasource": "$datasource",
32613919 "description": "Number of events queued up on the master process for processing by the federation sender",
3920 "fieldConfig": {
3921 "defaults": {
3922 "custom": {},
3923 "links": []
3924 },
3925 "overrides": []
3926 },
32623927 "fill": 1,
32633928 "fillGradient": 0,
32643929 "gridPos": {
32653930 "h": 9,
32663931 "w": 12,
3267 "x": 0,
3268 "y": 24
3932 "x": 12,
3933 "y": 51
32693934 },
32703935 "hiddenSeries": false,
32713936 "id": 140,
32823947 "linewidth": 1,
32833948 "links": [],
32843949 "nullPointMode": "null",
3285 "options": {
3286 "dataLinks": []
3287 },
32883950 "paceLength": 10,
32893951 "percentage": false,
3952 "pluginVersion": "7.1.3",
32903953 "pointradius": 5,
32913954 "points": false,
32923955 "renderer": "flot",
33914054 }
33924055 },
33934056 {
4057 "cards": {
4058 "cardPadding": -1,
4059 "cardRound": null
4060 },
4061 "color": {
4062 "cardColor": "#b4ff00",
4063 "colorScale": "sqrt",
4064 "colorScheme": "interpolateInferno",
4065 "exponent": 0.5,
4066 "min": 0,
4067 "mode": "spectrum"
4068 },
4069 "dataFormat": "tsbuckets",
4070 "datasource": "$datasource",
4071 "fieldConfig": {
4072 "defaults": {
4073 "custom": {}
4074 },
4075 "overrides": []
4076 },
4077 "gridPos": {
4078 "h": 9,
4079 "w": 12,
4080 "x": 0,
4081 "y": 59
4082 },
4083 "heatmap": {},
4084 "hideZeroBuckets": false,
4085 "highlightCards": true,
4086 "id": 166,
4087 "legend": {
4088 "show": false
4089 },
4090 "links": [],
4091 "reverseYBuckets": false,
4092 "targets": [
4093 {
4094 "expr": "sum(rate(synapse_event_processing_lag_by_event_bucket{instance=\"$instance\",name=\"federation_sender\"}[$bucket_size])) by (le)",
4095 "format": "heatmap",
4096 "instant": false,
4097 "interval": "",
4098 "intervalFactor": 1,
4099 "legendFormat": "{{ le }}",
4100 "refId": "A"
4101 }
4102 ],
4103 "title": "Federation send PDU lag",
4104 "tooltip": {
4105 "show": true,
4106 "showHistogram": true
4107 },
4108 "tooltipDecimals": 2,
4109 "type": "heatmap",
4110 "xAxis": {
4111 "show": true
4112 },
4113 "xBucketNumber": null,
4114 "xBucketSize": null,
4115 "yAxis": {
4116 "decimals": 0,
4117 "format": "s",
4118 "logBase": 1,
4119 "max": null,
4120 "min": null,
4121 "show": true,
4122 "splitFactor": null
4123 },
4124 "yBucketBound": "auto",
4125 "yBucketNumber": null,
4126 "yBucketSize": null
4127 },
4128 {
33944129 "aliasColors": {},
33954130 "bars": false,
33964131 "dashLength": 10,
33974132 "dashes": false,
3398 "datasource": "${DS_PROMETHEUS}",
3399 "description": "The number of events in the in-memory queues ",
3400 "fill": 1,
4133 "datasource": "$datasource",
4134 "fieldConfig": {
4135 "defaults": {
4136 "custom": {},
4137 "links": []
4138 },
4139 "overrides": []
4140 },
4141 "fill": 0,
34014142 "fillGradient": 0,
34024143 "gridPos": {
3403 "h": 8,
4144 "h": 9,
34044145 "w": 12,
34054146 "x": 12,
3406 "y": 24
4147 "y": 60
34074148 },
34084149 "hiddenSeries": false,
3409 "id": 142,
4150 "id": 162,
34104151 "legend": {
34114152 "avg": false,
34124153 "current": false,
34134154 "max": false,
34144155 "min": false,
4156 "rightSide": false,
34154157 "show": true,
34164158 "total": false,
34174159 "values": false
34184160 },
34194161 "lines": true,
3420 "linewidth": 1,
3421 "nullPointMode": "null",
3422 "options": {
3423 "dataLinks": []
3424 },
4162 "linewidth": 0,
4163 "links": [],
4164 "nullPointMode": "connected",
4165 "paceLength": 10,
34254166 "percentage": false,
3426 "pointradius": 2,
4167 "pluginVersion": "7.1.3",
4168 "pointradius": 5,
34274169 "points": false,
34284170 "renderer": "flot",
3429 "seriesOverrides": [],
4171 "seriesOverrides": [
4172 {
4173 "alias": "Avg",
4174 "fill": 0,
4175 "linewidth": 3
4176 },
4177 {
4178 "alias": "99%",
4179 "color": "#C4162A",
4180 "fillBelowTo": "90%"
4181 },
4182 {
4183 "alias": "90%",
4184 "color": "#FF7383",
4185 "fillBelowTo": "75%"
4186 },
4187 {
4188 "alias": "75%",
4189 "color": "#FFEE52",
4190 "fillBelowTo": "50%"
4191 },
4192 {
4193 "alias": "50%",
4194 "color": "#73BF69",
4195 "fillBelowTo": "25%"
4196 },
4197 {
4198 "alias": "25%",
4199 "color": "#1F60C4",
4200 "fillBelowTo": "5%"
4201 },
4202 {
4203 "alias": "5%",
4204 "lines": false
4205 },
4206 {
4207 "alias": "Average",
4208 "color": "rgb(255, 255, 255)",
4209 "lines": true,
4210 "linewidth": 3
4211 }
4212 ],
34304213 "spaceLength": 10,
34314214 "stack": false,
34324215 "steppedLine": false,
34334216 "targets": [
34344217 {
3435 "expr": "synapse_federation_transaction_queue_pending_pdus{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
4218 "expr": "histogram_quantile(0.99, sum(rate(synapse_event_processing_lag_by_event_bucket{name='federation_sender',index=~\"$index\",instance=\"$instance\"}[$bucket_size])) by (le))",
4219 "format": "time_series",
34364220 "interval": "",
3437 "legendFormat": "pending PDUs {{job}}-{{index}}",
4221 "intervalFactor": 1,
4222 "legendFormat": "99%",
4223 "refId": "D"
4224 },
4225 {
4226 "expr": "histogram_quantile(0.9, sum(rate(synapse_event_processing_lag_by_event_bucket{name='federation_sender',index=~\"$index\",instance=\"$instance\"}[$bucket_size])) by (le))",
4227 "format": "time_series",
4228 "interval": "",
4229 "intervalFactor": 1,
4230 "legendFormat": "90%",
34384231 "refId": "A"
34394232 },
34404233 {
3441 "expr": "synapse_federation_transaction_queue_pending_edus{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
4234 "expr": "histogram_quantile(0.75, sum(rate(synapse_event_processing_lag_by_event_bucket{name='federation_sender',index=~\"$index\",instance=\"$instance\"}[$bucket_size])) by (le))",
4235 "format": "time_series",
34424236 "interval": "",
3443 "legendFormat": "pending EDUs {{job}}-{{index}}",
4237 "intervalFactor": 1,
4238 "legendFormat": "75%",
4239 "refId": "C"
4240 },
4241 {
4242 "expr": "histogram_quantile(0.5, sum(rate(synapse_event_processing_lag_by_event_bucket{name='federation_sender',index=~\"$index\",instance=\"$instance\"}[$bucket_size])) by (le))",
4243 "format": "time_series",
4244 "interval": "",
4245 "intervalFactor": 1,
4246 "legendFormat": "50%",
34444247 "refId": "B"
3445 }
3446 ],
3447 "thresholds": [],
4248 },
4249 {
4250 "expr": "histogram_quantile(0.25, sum(rate(synapse_event_processing_lag_by_event_bucket{name='federation_sender',index=~\"$index\",instance=\"$instance\"}[$bucket_size])) by (le))",
4251 "interval": "",
4252 "legendFormat": "25%",
4253 "refId": "F"
4254 },
4255 {
4256 "expr": "histogram_quantile(0.05, sum(rate(synapse_event_processing_lag_by_event_bucket{name='federation_sender',index=~\"$index\",instance=\"$instance\"}[$bucket_size])) by (le))",
4257 "interval": "",
4258 "legendFormat": "5%",
4259 "refId": "G"
4260 },
4261 {
4262 "expr": "sum(rate(synapse_event_processing_lag_by_event_sum{name='federation_sender',index=~\"$index\",instance=\"$instance\"}[$bucket_size])) / sum(rate(synapse_event_processing_lag_by_event_count{name='federation_sender',index=~\"$index\",instance=\"$instance\"}[$bucket_size]))",
4263 "interval": "",
4264 "legendFormat": "Average",
4265 "refId": "H"
4266 }
4267 ],
4268 "thresholds": [
4269 {
4270 "colorMode": "warning",
4271 "fill": false,
4272 "line": true,
4273 "op": "gt",
4274 "value": 0.25,
4275 "yaxis": "left"
4276 },
4277 {
4278 "colorMode": "critical",
4279 "fill": false,
4280 "line": true,
4281 "op": "gt",
4282 "value": 1,
4283 "yaxis": "left"
4284 }
4285 ],
34484286 "timeFrom": null,
34494287 "timeRegions": [],
34504288 "timeShift": null,
3451 "title": "In-memory federation transmission queues",
4289 "title": "Federation send PDU lag quantiles",
34524290 "tooltip": {
34534291 "shared": true,
3454 "sort": 0,
4292 "sort": 2,
34554293 "value_type": "individual"
34564294 },
34574295 "type": "graph",
34644302 },
34654303 "yaxes": [
34664304 {
3467 "$$hashKey": "object:317",
3468 "format": "short",
3469 "label": "events",
4305 "decimals": null,
4306 "format": "s",
4307 "label": "",
34704308 "logBase": 1,
34714309 "max": null,
34724310 "min": "0",
34734311 "show": true
34744312 },
34754313 {
3476 "$$hashKey": "object:318",
3477 "format": "short",
4314 "format": "hertz",
34784315 "label": "",
34794316 "logBase": 1,
34804317 "max": null,
3481 "min": null,
4318 "min": "0",
34824319 "show": true
34834320 }
34844321 ],
34864323 "align": false,
34874324 "alignLevel": null
34884325 }
4326 },
4327 {
4328 "cards": {
4329 "cardPadding": -1,
4330 "cardRound": null
4331 },
4332 "color": {
4333 "cardColor": "#b4ff00",
4334 "colorScale": "sqrt",
4335 "colorScheme": "interpolateInferno",
4336 "exponent": 0.5,
4337 "min": 0,
4338 "mode": "spectrum"
4339 },
4340 "dataFormat": "tsbuckets",
4341 "datasource": "$datasource",
4342 "fieldConfig": {
4343 "defaults": {
4344 "custom": {}
4345 },
4346 "overrides": []
4347 },
4348 "gridPos": {
4349 "h": 9,
4350 "w": 12,
4351 "x": 0,
4352 "y": 68
4353 },
4354 "heatmap": {},
4355 "hideZeroBuckets": false,
4356 "highlightCards": true,
4357 "id": 164,
4358 "legend": {
4359 "show": false
4360 },
4361 "links": [],
4362 "reverseYBuckets": false,
4363 "targets": [
4364 {
4365 "expr": "sum(rate(synapse_federation_server_pdu_process_time_bucket{instance=\"$instance\"}[$bucket_size])) by (le)",
4366 "format": "heatmap",
4367 "instant": false,
4368 "interval": "",
4369 "intervalFactor": 1,
4370 "legendFormat": "{{ le }}",
4371 "refId": "A"
4372 }
4373 ],
4374 "title": "Handle inbound PDU time",
4375 "tooltip": {
4376 "show": true,
4377 "showHistogram": true
4378 },
4379 "tooltipDecimals": 2,
4380 "type": "heatmap",
4381 "xAxis": {
4382 "show": true
4383 },
4384 "xBucketNumber": null,
4385 "xBucketSize": null,
4386 "yAxis": {
4387 "decimals": 0,
4388 "format": "s",
4389 "logBase": 1,
4390 "max": null,
4391 "min": null,
4392 "show": true,
4393 "splitFactor": null
4394 },
4395 "yBucketBound": "auto",
4396 "yBucketNumber": null,
4397 "yBucketSize": null
34894398 }
34904399 ],
34914400 "title": "Federation",
34984407 "h": 1,
34994408 "w": 24,
35004409 "x": 0,
3501 "y": 33
4410 "y": 31
35024411 },
35034412 "id": 60,
35044413 "panels": [
35084417 "dashLength": 10,
35094418 "dashes": false,
35104419 "datasource": "$datasource",
4420 "fieldConfig": {
4421 "defaults": {
4422 "custom": {},
4423 "links": []
4424 },
4425 "overrides": []
4426 },
35114427 "fill": 1,
35124428 "fillGradient": 0,
35134429 "gridPos": {
35314447 "linewidth": 1,
35324448 "links": [],
35334449 "nullPointMode": "null",
3534 "options": {
3535 "dataLinks": []
3536 },
35374450 "paceLength": 10,
35384451 "percentage": false,
4452 "pluginVersion": "7.1.3",
35394453 "pointradius": 5,
35404454 "points": false,
35414455 "renderer": "flot",
36104524 "dashes": false,
36114525 "datasource": "$datasource",
36124526 "description": "",
4527 "fieldConfig": {
4528 "defaults": {
4529 "custom": {},
4530 "links": []
4531 },
4532 "overrides": []
4533 },
36134534 "fill": 1,
36144535 "fillGradient": 0,
36154536 "gridPos": {
36334554 "lines": true,
36344555 "linewidth": 1,
36354556 "nullPointMode": "null",
3636 "options": {
3637 "dataLinks": []
3638 },
36394557 "percentage": false,
4558 "pluginVersion": "7.1.3",
36404559 "pointradius": 2,
36414560 "points": false,
36424561 "renderer": "flot",
37044623 "h": 1,
37054624 "w": 24,
37064625 "x": 0,
3707 "y": 34
4626 "y": 32
37084627 },
37094628 "id": 58,
37104629 "panels": [
37144633 "dashLength": 10,
37154634 "dashes": false,
37164635 "datasource": "$datasource",
4636 "fieldConfig": {
4637 "defaults": {
4638 "custom": {},
4639 "links": []
4640 },
4641 "overrides": []
4642 },
37174643 "fill": 1,
37184644 "fillGradient": 0,
37194645 "gridPos": {
37204646 "h": 7,
37214647 "w": 12,
37224648 "x": 0,
3723 "y": 79
4649 "y": 8
37244650 },
37254651 "hiddenSeries": false,
37264652 "id": 48,
37384664 "links": [],
37394665 "nullPointMode": "null",
37404666 "options": {
3741 "dataLinks": []
4667 "alertThreshold": true
37424668 },
37434669 "paceLength": 10,
37444670 "percentage": false,
4671 "pluginVersion": "7.3.7",
37454672 "pointradius": 5,
37464673 "points": false,
37474674 "renderer": "flot",
38084735 "dashes": false,
38094736 "datasource": "$datasource",
38104737 "description": "Shows the time in which the given percentage of database queries were scheduled, over the sampled timespan",
4738 "fieldConfig": {
4739 "defaults": {
4740 "custom": {},
4741 "links": []
4742 },
4743 "overrides": []
4744 },
38114745 "fill": 1,
38124746 "fillGradient": 0,
38134747 "gridPos": {
38144748 "h": 7,
38154749 "w": 12,
38164750 "x": 12,
3817 "y": 79
4751 "y": 8
38184752 },
38194753 "hiddenSeries": false,
38204754 "id": 104,
38334767 "links": [],
38344768 "nullPointMode": "null",
38354769 "options": {
3836 "dataLinks": []
4770 "alertThreshold": true
38374771 },
38384772 "paceLength": 10,
38394773 "percentage": false,
4774 "pluginVersion": "7.3.7",
38404775 "pointradius": 5,
38414776 "points": false,
38424777 "renderer": "flot",
39274862 "datasource": "$datasource",
39284863 "editable": true,
39294864 "error": false,
4865 "fieldConfig": {
4866 "defaults": {
4867 "custom": {},
4868 "links": []
4869 },
4870 "overrides": []
4871 },
39304872 "fill": 0,
39314873 "fillGradient": 0,
39324874 "grid": {},
39344876 "h": 7,
39354877 "w": 12,
39364878 "x": 0,
3937 "y": 86
4879 "y": 15
39384880 },
39394881 "hiddenSeries": false,
39404882 "id": 10,
39544896 "links": [],
39554897 "nullPointMode": "null",
39564898 "options": {
3957 "dataLinks": []
4899 "alertThreshold": true
39584900 },
39594901 "paceLength": 10,
39604902 "percentage": false,
4903 "pluginVersion": "7.3.7",
39614904 "pointradius": 5,
39624905 "points": false,
39634906 "renderer": "flot",
40234966 "datasource": "$datasource",
40244967 "editable": true,
40254968 "error": false,
4969 "fieldConfig": {
4970 "defaults": {
4971 "custom": {},
4972 "links": []
4973 },
4974 "overrides": []
4975 },
40264976 "fill": 1,
40274977 "fillGradient": 0,
40284978 "grid": {},
40304980 "h": 7,
40314981 "w": 12,
40324982 "x": 12,
4033 "y": 86
4983 "y": 15
40344984 },
40354985 "hiddenSeries": false,
40364986 "id": 11,
40505000 "links": [],
40515001 "nullPointMode": "null",
40525002 "options": {
4053 "dataLinks": []
5003 "alertThreshold": true
40545004 },
40555005 "paceLength": 10,
40565006 "percentage": false,
5007 "pluginVersion": "7.3.7",
40575008 "pointradius": 5,
40585009 "points": false,
40595010 "renderer": "flot",
40775028 "timeFrom": null,
40785029 "timeRegions": [],
40795030 "timeShift": null,
4080 "title": "Top DB transactions by total txn time",
5031 "title": "DB transactions by total txn time",
40815032 "tooltip": {
40825033 "shared": false,
40835034 "sort": 0,
40945045 "yaxes": [
40955046 {
40965047 "format": "percentunit",
5048 "logBase": 1,
5049 "max": null,
5050 "min": null,
5051 "show": true
5052 },
5053 {
5054 "format": "short",
5055 "logBase": 1,
5056 "max": null,
5057 "min": null,
5058 "show": true
5059 }
5060 ],
5061 "yaxis": {
5062 "align": false,
5063 "alignLevel": null
5064 }
5065 },
5066 {
5067 "aliasColors": {},
5068 "bars": false,
5069 "dashLength": 10,
5070 "dashes": false,
5071 "datasource": "$datasource",
5072 "editable": true,
5073 "error": false,
5074 "fieldConfig": {
5075 "defaults": {
5076 "custom": {},
5077 "links": []
5078 },
5079 "overrides": []
5080 },
5081 "fill": 0,
5082 "fillGradient": 0,
5083 "grid": {},
5084 "gridPos": {
5085 "h": 7,
5086 "w": 12,
5087 "x": 0,
5088 "y": 22
5089 },
5090 "hiddenSeries": false,
5091 "id": 180,
5092 "legend": {
5093 "avg": false,
5094 "current": false,
5095 "hideEmpty": true,
5096 "hideZero": true,
5097 "max": false,
5098 "min": false,
5099 "show": true,
5100 "total": false,
5101 "values": false
5102 },
5103 "lines": true,
5104 "linewidth": 1,
5105 "links": [],
5106 "nullPointMode": "null",
5107 "options": {
5108 "alertThreshold": false
5109 },
5110 "paceLength": 10,
5111 "percentage": false,
5112 "pluginVersion": "7.3.7",
5113 "pointradius": 5,
5114 "points": false,
5115 "renderer": "flot",
5116 "seriesOverrides": [],
5117 "spaceLength": 10,
5118 "stack": false,
5119 "steppedLine": false,
5120 "targets": [
5121 {
5122 "expr": "rate(synapse_storage_transaction_time_sum{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])/rate(synapse_storage_transaction_time_count{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
5123 "format": "time_series",
5124 "instant": false,
5125 "interval": "",
5126 "intervalFactor": 1,
5127 "legendFormat": "{{job}}-{{index}} {{desc}}",
5128 "refId": "A",
5129 "step": 20
5130 }
5131 ],
5132 "thresholds": [],
5133 "timeFrom": null,
5134 "timeRegions": [],
5135 "timeShift": null,
5136 "title": "Average DB txn time",
5137 "tooltip": {
5138 "shared": false,
5139 "sort": 0,
5140 "value_type": "cumulative"
5141 },
5142 "type": "graph",
5143 "xaxis": {
5144 "buckets": null,
5145 "mode": "time",
5146 "name": null,
5147 "show": true,
5148 "values": []
5149 },
5150 "yaxes": [
5151 {
5152 "format": "s",
40975153 "logBase": 1,
40985154 "max": null,
40995155 "min": null,
41245180 "h": 1,
41255181 "w": 24,
41265182 "x": 0,
4127 "y": 35
5183 "y": 33
41285184 },
41295185 "id": 59,
41305186 "panels": [
41365192 "datasource": "$datasource",
41375193 "editable": true,
41385194 "error": false,
5195 "fieldConfig": {
5196 "defaults": {
5197 "custom": {},
5198 "links": []
5199 },
5200 "overrides": []
5201 },
41395202 "fill": 1,
41405203 "fillGradient": 0,
41415204 "grid": {},
41435206 "h": 13,
41445207 "w": 12,
41455208 "x": 0,
4146 "y": 80
5209 "y": 9
41475210 },
41485211 "hiddenSeries": false,
41495212 "id": 12,
41615224 "linewidth": 2,
41625225 "links": [],
41635226 "nullPointMode": "null",
4164 "options": {
4165 "dataLinks": []
4166 },
41675227 "paceLength": 10,
41685228 "percentage": false,
5229 "pluginVersion": "7.1.3",
41695230 "pointradius": 5,
41705231 "points": false,
41715232 "renderer": "flot",
41905251 "timeShift": null,
41915252 "title": "Total CPU Usage by Block",
41925253 "tooltip": {
4193 "shared": false,
4194 "sort": 0,
5254 "shared": true,
5255 "sort": 2,
41955256 "value_type": "cumulative"
41965257 },
41975258 "type": "graph",
42315292 "datasource": "$datasource",
42325293 "editable": true,
42335294 "error": false,
5295 "fieldConfig": {
5296 "defaults": {
5297 "custom": {},
5298 "links": []
5299 },
5300 "overrides": []
5301 },
42345302 "fill": 1,
42355303 "fillGradient": 0,
42365304 "grid": {},
42385306 "h": 13,
42395307 "w": 12,
42405308 "x": 12,
4241 "y": 80
5309 "y": 9
42425310 },
42435311 "hiddenSeries": false,
42445312 "id": 26,
42565324 "linewidth": 2,
42575325 "links": [],
42585326 "nullPointMode": "null",
4259 "options": {
4260 "dataLinks": []
4261 },
42625327 "paceLength": 10,
42635328 "percentage": false,
5329 "pluginVersion": "7.1.3",
42645330 "pointradius": 5,
42655331 "points": false,
42665332 "renderer": "flot",
42855351 "timeShift": null,
42865352 "title": "Average CPU Time per Block",
42875353 "tooltip": {
4288 "shared": false,
4289 "sort": 0,
5354 "shared": true,
5355 "sort": 2,
42905356 "value_type": "cumulative"
42915357 },
42925358 "type": "graph",
43265392 "datasource": "$datasource",
43275393 "editable": true,
43285394 "error": false,
5395 "fieldConfig": {
5396 "defaults": {
5397 "custom": {},
5398 "links": []
5399 },
5400 "overrides": []
5401 },
43295402 "fill": 1,
43305403 "fillGradient": 0,
43315404 "grid": {},
43335406 "h": 13,
43345407 "w": 12,
43355408 "x": 0,
4336 "y": 93
5409 "y": 22
43375410 },
43385411 "hiddenSeries": false,
43395412 "id": 13,
43515424 "linewidth": 2,
43525425 "links": [],
43535426 "nullPointMode": "null",
4354 "options": {
4355 "dataLinks": []
4356 },
43575427 "paceLength": 10,
43585428 "percentage": false,
5429 "pluginVersion": "7.1.3",
43595430 "pointradius": 5,
43605431 "points": false,
43615432 "renderer": "flot",
43805451 "timeShift": null,
43815452 "title": "Total DB Usage by Block",
43825453 "tooltip": {
4383 "shared": false,
4384 "sort": 0,
5454 "shared": true,
5455 "sort": 2,
43855456 "value_type": "cumulative"
43865457 },
43875458 "type": "graph",
44225493 "description": "The time each database transaction takes to execute, on average, broken down by metrics block.",
44235494 "editable": true,
44245495 "error": false,
5496 "fieldConfig": {
5497 "defaults": {
5498 "custom": {},
5499 "links": []
5500 },
5501 "overrides": []
5502 },
44255503 "fill": 1,
44265504 "fillGradient": 0,
44275505 "grid": {},
44295507 "h": 13,
44305508 "w": 12,
44315509 "x": 12,
4432 "y": 93
5510 "y": 22
44335511 },
44345512 "hiddenSeries": false,
44355513 "id": 27,
44475525 "linewidth": 2,
44485526 "links": [],
44495527 "nullPointMode": "null",
4450 "options": {
4451 "dataLinks": []
4452 },
44535528 "paceLength": 10,
44545529 "percentage": false,
5530 "pluginVersion": "7.1.3",
44555531 "pointradius": 5,
44565532 "points": false,
44575533 "renderer": "flot",
44765552 "timeShift": null,
44775553 "title": "Average Database Transaction time, by Block",
44785554 "tooltip": {
4479 "shared": false,
4480 "sort": 0,
5555 "shared": true,
5556 "sort": 2,
44815557 "value_type": "cumulative"
44825558 },
44835559 "type": "graph",
45175593 "datasource": "$datasource",
45185594 "editable": true,
45195595 "error": false,
5596 "fieldConfig": {
5597 "defaults": {
5598 "custom": {},
5599 "links": []
5600 },
5601 "overrides": []
5602 },
45205603 "fill": 1,
45215604 "fillGradient": 0,
45225605 "grid": {},
45245607 "h": 13,
45255608 "w": 12,
45265609 "x": 0,
4527 "y": 106
5610 "y": 35
45285611 },
45295612 "hiddenSeries": false,
45305613 "id": 28,
45415624 "linewidth": 2,
45425625 "links": [],
45435626 "nullPointMode": "null",
4544 "options": {
4545 "dataLinks": []
4546 },
45475627 "paceLength": 10,
45485628 "percentage": false,
5629 "pluginVersion": "7.1.3",
45495630 "pointradius": 5,
45505631 "points": false,
45515632 "renderer": "flot",
46115692 "datasource": "$datasource",
46125693 "editable": true,
46135694 "error": false,
5695 "fieldConfig": {
5696 "defaults": {
5697 "custom": {},
5698 "links": []
5699 },
5700 "overrides": []
5701 },
46145702 "fill": 1,
46155703 "fillGradient": 0,
46165704 "grid": {},
46185706 "h": 13,
46195707 "w": 12,
46205708 "x": 12,
4621 "y": 106
5709 "y": 35
46225710 },
46235711 "hiddenSeries": false,
46245712 "id": 25,
46355723 "linewidth": 2,
46365724 "links": [],
46375725 "nullPointMode": "null",
4638 "options": {
4639 "dataLinks": []
4640 },
46415726 "paceLength": 10,
46425727 "percentage": false,
5728 "pluginVersion": "7.1.3",
46435729 "pointradius": 5,
46445730 "points": false,
46455731 "renderer": "flot",
46865772 },
46875773 {
46885774 "format": "short",
5775 "logBase": 1,
5776 "max": null,
5777 "min": null,
5778 "show": true
5779 }
5780 ],
5781 "yaxis": {
5782 "align": false,
5783 "alignLevel": null
5784 }
5785 },
5786 {
5787 "aliasColors": {},
5788 "bars": false,
5789 "dashLength": 10,
5790 "dashes": false,
5791 "datasource": "$datasource",
5792 "fieldConfig": {
5793 "defaults": {
5794 "custom": {}
5795 },
5796 "overrides": []
5797 },
5798 "fill": 1,
5799 "fillGradient": 0,
5800 "gridPos": {
5801 "h": 15,
5802 "w": 12,
5803 "x": 0,
5804 "y": 48
5805 },
5806 "hiddenSeries": false,
5807 "id": 154,
5808 "legend": {
5809 "alignAsTable": true,
5810 "avg": false,
5811 "current": false,
5812 "max": false,
5813 "min": false,
5814 "show": true,
5815 "total": false,
5816 "values": false
5817 },
5818 "lines": true,
5819 "linewidth": 1,
5820 "nullPointMode": "null",
5821 "percentage": false,
5822 "pluginVersion": "7.1.3",
5823 "pointradius": 2,
5824 "points": false,
5825 "renderer": "flot",
5826 "seriesOverrides": [],
5827 "spaceLength": 10,
5828 "stack": false,
5829 "steppedLine": false,
5830 "targets": [
5831 {
5832 "expr": "rate(synapse_util_metrics_block_count{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
5833 "interval": "",
5834 "legendFormat": "{{job}}-{{index}} {{block_name}}",
5835 "refId": "A"
5836 }
5837 ],
5838 "thresholds": [],
5839 "timeFrom": null,
5840 "timeRegions": [],
5841 "timeShift": null,
5842 "title": "Block count",
5843 "tooltip": {
5844 "shared": true,
5845 "sort": 2,
5846 "value_type": "individual"
5847 },
5848 "type": "graph",
5849 "xaxis": {
5850 "buckets": null,
5851 "mode": "time",
5852 "name": null,
5853 "show": true,
5854 "values": []
5855 },
5856 "yaxes": [
5857 {
5858 "format": "hertz",
5859 "label": null,
5860 "logBase": 1,
5861 "max": null,
5862 "min": null,
5863 "show": true
5864 },
5865 {
5866 "format": "short",
5867 "label": null,
46895868 "logBase": 1,
46905869 "max": null,
46915870 "min": null,
47095888 "h": 1,
47105889 "w": 24,
47115890 "x": 0,
4712 "y": 36
5891 "y": 34
47135892 },
47145893 "id": 61,
47155894 "panels": [
47225901 "decimals": 2,
47235902 "editable": true,
47245903 "error": false,
5904 "fieldConfig": {
5905 "defaults": {
5906 "custom": {},
5907 "links": []
5908 },
5909 "overrides": []
5910 },
47255911 "fill": 0,
47265912 "fillGradient": 0,
47275913 "grid": {},
47295915 "h": 10,
47305916 "w": 12,
47315917 "x": 0,
4732 "y": 37
5918 "y": 84
47335919 },
47345920 "hiddenSeries": false,
47355921 "id": 1,
47505936 "links": [],
47515937 "nullPointMode": "null",
47525938 "options": {
4753 "dataLinks": []
5939 "alertThreshold": true
47545940 },
47555941 "percentage": false,
5942 "pluginVersion": "7.3.7",
47565943 "pointradius": 5,
47575944 "points": false,
47585945 "renderer": "flot",
48206007 "datasource": "$datasource",
48216008 "editable": true,
48226009 "error": false,
6010 "fieldConfig": {
6011 "defaults": {
6012 "custom": {},
6013 "links": []
6014 },
6015 "overrides": []
6016 },
48236017 "fill": 1,
48246018 "fillGradient": 0,
48256019 "grid": {},
48276021 "h": 10,
48286022 "w": 12,
48296023 "x": 12,
4830 "y": 37
6024 "y": 84
48316025 },
48326026 "hiddenSeries": false,
48336027 "id": 8,
48476041 "links": [],
48486042 "nullPointMode": "connected",
48496043 "options": {
4850 "dataLinks": []
6044 "alertThreshold": true
48516045 },
48526046 "percentage": false,
6047 "pluginVersion": "7.3.7",
48536048 "pointradius": 5,
48546049 "points": false,
48556050 "renderer": "flot",
49166111 "datasource": "$datasource",
49176112 "editable": true,
49186113 "error": false,
6114 "fieldConfig": {
6115 "defaults": {
6116 "custom": {},
6117 "links": []
6118 },
6119 "overrides": []
6120 },
49196121 "fill": 1,
49206122 "fillGradient": 0,
49216123 "grid": {},
49236125 "h": 10,
49246126 "w": 12,
49256127 "x": 0,
4926 "y": 47
6128 "y": 94
49276129 },
49286130 "hiddenSeries": false,
49296131 "id": 38,
49436145 "links": [],
49446146 "nullPointMode": "connected",
49456147 "options": {
4946 "dataLinks": []
6148 "alertThreshold": true
49476149 },
49486150 "percentage": false,
6151 "pluginVersion": "7.3.7",
49496152 "pointradius": 5,
49506153 "points": false,
49516154 "renderer": "flot",
50096212 "dashLength": 10,
50106213 "dashes": false,
50116214 "datasource": "$datasource",
6215 "fieldConfig": {
6216 "defaults": {
6217 "custom": {},
6218 "links": []
6219 },
6220 "overrides": []
6221 },
50126222 "fill": 1,
50136223 "fillGradient": 0,
50146224 "gridPos": {
50156225 "h": 10,
50166226 "w": 12,
50176227 "x": 12,
5018 "y": 47
6228 "y": 94
50196229 },
50206230 "hiddenSeries": false,
50216231 "id": 39,
50346244 "links": [],
50356245 "nullPointMode": "null",
50366246 "options": {
5037 "dataLinks": []
6247 "alertThreshold": true
50386248 },
50396249 "percentage": false,
6250 "pluginVersion": "7.3.7",
50406251 "pointradius": 5,
50416252 "points": false,
50426253 "renderer": "flot",
51016312 "dashLength": 10,
51026313 "dashes": false,
51036314 "datasource": "$datasource",
6315 "fieldConfig": {
6316 "defaults": {
6317 "custom": {},
6318 "links": []
6319 },
6320 "overrides": []
6321 },
51046322 "fill": 1,
51056323 "fillGradient": 0,
51066324 "gridPos": {
51076325 "h": 9,
51086326 "w": 12,
51096327 "x": 0,
5110 "y": 57
6328 "y": 104
51116329 },
51126330 "hiddenSeries": false,
51136331 "id": 65,
51266344 "links": [],
51276345 "nullPointMode": "null",
51286346 "options": {
5129 "dataLinks": []
6347 "alertThreshold": true
51306348 },
51316349 "percentage": false,
6350 "pluginVersion": "7.3.7",
51326351 "pointradius": 5,
51336352 "points": false,
51346353 "renderer": "flot",
51996418 "h": 1,
52006419 "w": 24,
52016420 "x": 0,
5202 "y": 37
6421 "y": 35
52036422 },
5204 "id": 62,
6423 "id": 148,
52056424 "panels": [
52066425 {
52076426 "aliasColors": {},
52096428 "dashLength": 10,
52106429 "dashes": false,
52116430 "datasource": "$datasource",
6431 "fieldConfig": {
6432 "defaults": {
6433 "custom": {},
6434 "links": []
6435 },
6436 "overrides": []
6437 },
6438 "fill": 1,
6439 "fillGradient": 0,
6440 "gridPos": {
6441 "h": 8,
6442 "w": 12,
6443 "x": 0,
6444 "y": 29
6445 },
6446 "hiddenSeries": false,
6447 "id": 146,
6448 "legend": {
6449 "avg": false,
6450 "current": false,
6451 "max": false,
6452 "min": false,
6453 "show": true,
6454 "total": false,
6455 "values": false
6456 },
6457 "lines": true,
6458 "linewidth": 1,
6459 "nullPointMode": "null",
6460 "options": {
6461 "alertThreshold": true
6462 },
6463 "percentage": false,
6464 "pluginVersion": "7.3.7",
6465 "pointradius": 2,
6466 "points": false,
6467 "renderer": "flot",
6468 "seriesOverrides": [],
6469 "spaceLength": 10,
6470 "stack": false,
6471 "steppedLine": false,
6472 "targets": [
6473 {
6474 "expr": "synapse_util_caches_response_cache:size{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
6475 "interval": "",
6476 "legendFormat": "{{name}} {{job}}-{{index}}",
6477 "refId": "A"
6478 }
6479 ],
6480 "thresholds": [],
6481 "timeFrom": null,
6482 "timeRegions": [],
6483 "timeShift": null,
6484 "title": "Response cache size",
6485 "tooltip": {
6486 "shared": false,
6487 "sort": 0,
6488 "value_type": "individual"
6489 },
6490 "type": "graph",
6491 "xaxis": {
6492 "buckets": null,
6493 "mode": "time",
6494 "name": null,
6495 "show": true,
6496 "values": []
6497 },
6498 "yaxes": [
6499 {
6500 "format": "short",
6501 "label": null,
6502 "logBase": 1,
6503 "max": null,
6504 "min": null,
6505 "show": true
6506 },
6507 {
6508 "format": "short",
6509 "label": null,
6510 "logBase": 1,
6511 "max": null,
6512 "min": null,
6513 "show": true
6514 }
6515 ],
6516 "yaxis": {
6517 "align": false,
6518 "alignLevel": null
6519 }
6520 },
6521 {
6522 "aliasColors": {},
6523 "bars": false,
6524 "dashLength": 10,
6525 "dashes": false,
6526 "datasource": "$datasource",
6527 "fieldConfig": {
6528 "defaults": {
6529 "custom": {},
6530 "links": []
6531 },
6532 "overrides": []
6533 },
6534 "fill": 1,
6535 "fillGradient": 0,
6536 "gridPos": {
6537 "h": 8,
6538 "w": 12,
6539 "x": 12,
6540 "y": 29
6541 },
6542 "hiddenSeries": false,
6543 "id": 150,
6544 "legend": {
6545 "avg": false,
6546 "current": false,
6547 "max": false,
6548 "min": false,
6549 "show": true,
6550 "total": false,
6551 "values": false
6552 },
6553 "lines": true,
6554 "linewidth": 1,
6555 "nullPointMode": "null",
6556 "options": {
6557 "alertThreshold": true
6558 },
6559 "percentage": false,
6560 "pluginVersion": "7.3.7",
6561 "pointradius": 2,
6562 "points": false,
6563 "renderer": "flot",
6564 "seriesOverrides": [],
6565 "spaceLength": 10,
6566 "stack": false,
6567 "steppedLine": false,
6568 "targets": [
6569 {
6570 "expr": "rate(synapse_util_caches_response_cache:hits{instance=\"$instance\", job=~\"$job\", index=~\"$index\"}[$bucket_size])/rate(synapse_util_caches_response_cache:total{instance=\"$instance\", job=~\"$job\", index=~\"$index\"}[$bucket_size])",
6571 "interval": "",
6572 "legendFormat": "{{name}} {{job}}-{{index}}",
6573 "refId": "A"
6574 },
6575 {
6576 "expr": "",
6577 "interval": "",
6578 "legendFormat": "",
6579 "refId": "B"
6580 }
6581 ],
6582 "thresholds": [],
6583 "timeFrom": null,
6584 "timeRegions": [],
6585 "timeShift": null,
6586 "title": "Response cache hit rate",
6587 "tooltip": {
6588 "shared": false,
6589 "sort": 0,
6590 "value_type": "individual"
6591 },
6592 "type": "graph",
6593 "xaxis": {
6594 "buckets": null,
6595 "mode": "time",
6596 "name": null,
6597 "show": true,
6598 "values": []
6599 },
6600 "yaxes": [
6601 {
6602 "decimals": null,
6603 "format": "percentunit",
6604 "label": null,
6605 "logBase": 1,
6606 "max": "1",
6607 "min": "0",
6608 "show": true
6609 },
6610 {
6611 "format": "short",
6612 "label": null,
6613 "logBase": 1,
6614 "max": null,
6615 "min": null,
6616 "show": true
6617 }
6618 ],
6619 "yaxis": {
6620 "align": false,
6621 "alignLevel": null
6622 }
6623 }
6624 ],
6625 "title": "Response caches",
6626 "type": "row"
6627 },
6628 {
6629 "collapsed": true,
6630 "datasource": "${DS_PROMETHEUS}",
6631 "gridPos": {
6632 "h": 1,
6633 "w": 24,
6634 "x": 0,
6635 "y": 36
6636 },
6637 "id": 62,
6638 "panels": [
6639 {
6640 "aliasColors": {},
6641 "bars": false,
6642 "dashLength": 10,
6643 "dashes": false,
6644 "datasource": "$datasource",
6645 "fieldConfig": {
6646 "defaults": {
6647 "custom": {},
6648 "links": []
6649 },
6650 "overrides": []
6651 },
52126652 "fill": 1,
52136653 "fillGradient": 0,
52146654 "gridPos": {
52156655 "h": 9,
52166656 "w": 12,
52176657 "x": 0,
5218 "y": 121
6658 "y": 30
52196659 },
52206660 "hiddenSeries": false,
52216661 "id": 91,
52336673 "links": [],
52346674 "nullPointMode": "null",
52356675 "options": {
5236 "dataLinks": []
6676 "alertThreshold": true
52376677 },
52386678 "percentage": false,
6679 "pluginVersion": "7.3.7",
52396680 "pointradius": 5,
52406681 "points": false,
52416682 "renderer": "flot",
53046745 "decimals": 3,
53056746 "editable": true,
53066747 "error": false,
6748 "fieldConfig": {
6749 "defaults": {
6750 "custom": {},
6751 "links": []
6752 },
6753 "overrides": []
6754 },
53076755 "fill": 1,
53086756 "fillGradient": 0,
53096757 "grid": {},
53116759 "h": 9,
53126760 "w": 12,
53136761 "x": 12,
5314 "y": 121
6762 "y": 30
53156763 },
53166764 "hiddenSeries": false,
53176765 "id": 21,
53306778 "links": [],
53316779 "nullPointMode": "null as zero",
53326780 "options": {
5333 "dataLinks": []
6781 "alertThreshold": true
53346782 },
53356783 "percentage": false,
6784 "pluginVersion": "7.3.7",
53366785 "pointradius": 5,
53376786 "points": false,
53386787 "renderer": "flot",
53976846 "dashes": false,
53986847 "datasource": "$datasource",
53996848 "description": "'gen 0' shows the number of objects allocated since the last gen0 GC.\n'gen 1' / 'gen 2' show the number of gen0/gen1 GCs since the last gen1/gen2 GC.",
6849 "fieldConfig": {
6850 "defaults": {
6851 "custom": {},
6852 "links": []
6853 },
6854 "overrides": []
6855 },
54006856 "fill": 1,
54016857 "fillGradient": 0,
54026858 "gridPos": {
54036859 "h": 9,
54046860 "w": 12,
54056861 "x": 0,
5406 "y": 130
6862 "y": 39
54076863 },
54086864 "hiddenSeries": false,
54096865 "id": 89,
54236879 "links": [],
54246880 "nullPointMode": "null",
54256881 "options": {
5426 "dataLinks": []
6882 "alertThreshold": true
54276883 },
54286884 "percentage": false,
6885 "pluginVersion": "7.3.7",
54296886 "pointradius": 5,
54306887 "points": false,
54316888 "renderer": "flot",
54956952 "dashLength": 10,
54966953 "dashes": false,
54976954 "datasource": "$datasource",
6955 "fieldConfig": {
6956 "defaults": {
6957 "custom": {},
6958 "links": []
6959 },
6960 "overrides": []
6961 },
54986962 "fill": 1,
54996963 "fillGradient": 0,
55006964 "gridPos": {
55016965 "h": 9,
55026966 "w": 12,
55036967 "x": 12,
5504 "y": 130
6968 "y": 39
55056969 },
55066970 "hiddenSeries": false,
55076971 "id": 93,
55196983 "links": [],
55206984 "nullPointMode": "connected",
55216985 "options": {
5522 "dataLinks": []
6986 "alertThreshold": true
55236987 },
55246988 "percentage": false,
6989 "pluginVersion": "7.3.7",
55256990 "pointradius": 5,
55266991 "points": false,
55276992 "renderer": "flot",
55857050 "dashLength": 10,
55867051 "dashes": false,
55877052 "datasource": "$datasource",
7053 "fieldConfig": {
7054 "defaults": {
7055 "custom": {},
7056 "links": []
7057 },
7058 "overrides": []
7059 },
55887060 "fill": 1,
55897061 "fillGradient": 0,
55907062 "gridPos": {
55917063 "h": 9,
55927064 "w": 12,
55937065 "x": 0,
5594 "y": 139
7066 "y": 48
55957067 },
55967068 "hiddenSeries": false,
55977069 "id": 95,
56097081 "links": [],
56107082 "nullPointMode": "null",
56117083 "options": {
5612 "dataLinks": []
7084 "alertThreshold": true
56137085 },
56147086 "percentage": false,
7087 "pluginVersion": "7.3.7",
56157088 "pointradius": 5,
56167089 "points": false,
56177090 "renderer": "flot",
56857158 },
56867159 "dataFormat": "tsbuckets",
56877160 "datasource": "${DS_PROMETHEUS}",
7161 "fieldConfig": {
7162 "defaults": {
7163 "custom": {}
7164 },
7165 "overrides": []
7166 },
56887167 "gridPos": {
56897168 "h": 9,
56907169 "w": 12,
56917170 "x": 12,
5692 "y": 139
7171 "y": 48
56937172 },
56947173 "heatmap": {},
56957174 "hideZeroBuckets": true,
57457224 "h": 1,
57467225 "w": 24,
57477226 "x": 0,
5748 "y": 38
7227 "y": 37
57497228 },
57507229 "id": 63,
57517230 "panels": [
57557234 "dashLength": 10,
57567235 "dashes": false,
57577236 "datasource": "$datasource",
7237 "fieldConfig": {
7238 "defaults": {
7239 "custom": {},
7240 "links": []
7241 },
7242 "overrides": []
7243 },
57587244 "fill": 1,
57597245 "fillGradient": 0,
57607246 "gridPos": {
57617247 "h": 7,
57627248 "w": 12,
57637249 "x": 0,
5764 "y": 66
7250 "y": 13
57657251 },
57667252 "hiddenSeries": false,
5767 "id": 2,
7253 "id": 42,
57687254 "legend": {
57697255 "avg": false,
57707256 "current": false,
57797265 "links": [],
57807266 "nullPointMode": "null",
57817267 "options": {
5782 "dataLinks": []
7268 "alertThreshold": true
57837269 },
57847270 "paceLength": 10,
57857271 "percentage": false,
7272 "pluginVersion": "7.3.7",
57867273 "pointradius": 5,
57877274 "points": false,
57887275 "renderer": "flot",
57927279 "steppedLine": false,
57937280 "targets": [
57947281 {
5795 "expr": "rate(synapse_replication_tcp_resource_user_sync{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
7282 "expr": "sum (rate(synapse_replication_tcp_protocol_inbound_commands{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (name, conn_id)",
57967283 "format": "time_series",
57977284 "intervalFactor": 2,
5798 "legendFormat": "user started/stopped syncing",
7285 "legendFormat": "{{job}}-{{index}} {{command}}",
57997286 "refId": "A",
5800 "step": 20
5801 },
5802 {
5803 "expr": "rate(synapse_replication_tcp_resource_federation_ack{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
5804 "format": "time_series",
5805 "intervalFactor": 2,
5806 "legendFormat": "federation ack",
5807 "refId": "B",
5808 "step": 20
5809 },
5810 {
5811 "expr": "rate(synapse_replication_tcp_resource_remove_pusher{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
5812 "format": "time_series",
5813 "intervalFactor": 2,
5814 "legendFormat": "remove pusher",
5815 "refId": "C",
5816 "step": 20
5817 },
5818 {
5819 "expr": "rate(synapse_replication_tcp_resource_invalidate_cache{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
5820 "format": "time_series",
5821 "intervalFactor": 2,
5822 "legendFormat": "invalidate cache",
5823 "refId": "D",
5824 "step": 20
5825 },
5826 {
5827 "expr": "rate(synapse_replication_tcp_resource_user_ip_cache{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])",
5828 "format": "time_series",
5829 "intervalFactor": 2,
5830 "legendFormat": "user ip cache",
5831 "refId": "E",
58327287 "step": 20
58337288 }
58347289 ],
58367291 "timeFrom": null,
58377292 "timeRegions": [],
58387293 "timeShift": null,
5839 "title": "Rate of events on replication master",
7294 "title": "Rate of incoming commands",
58407295 "tooltip": {
58417296 "shared": false,
58427297 "sort": 0,
58787333 "bars": false,
58797334 "dashLength": 10,
58807335 "dashes": false,
5881 "datasource": "$datasource",
7336 "datasource": "${DS_PROMETHEUS}",
7337 "description": "",
7338 "fieldConfig": {
7339 "defaults": {
7340 "custom": {},
7341 "links": []
7342 },
7343 "overrides": []
7344 },
58827345 "fill": 1,
58837346 "fillGradient": 0,
58847347 "gridPos": {
58857348 "h": 7,
58867349 "w": 12,
58877350 "x": 12,
5888 "y": 66
7351 "y": 13
7352 },
7353 "hiddenSeries": false,
7354 "id": 144,
7355 "legend": {
7356 "avg": false,
7357 "current": false,
7358 "max": false,
7359 "min": false,
7360 "show": true,
7361 "total": false,
7362 "values": false
7363 },
7364 "lines": true,
7365 "linewidth": 1,
7366 "nullPointMode": "null",
7367 "options": {
7368 "alertThreshold": true
7369 },
7370 "percentage": false,
7371 "pluginVersion": "7.3.7",
7372 "pointradius": 2,
7373 "points": false,
7374 "renderer": "flot",
7375 "seriesOverrides": [],
7376 "spaceLength": 10,
7377 "stack": false,
7378 "steppedLine": false,
7379 "targets": [
7380 {
7381 "expr": "synapse_replication_tcp_command_queue{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
7382 "interval": "",
7383 "legendFormat": "{{stream_name}} {{job}}-{{index}}",
7384 "refId": "A"
7385 }
7386 ],
7387 "thresholds": [],
7388 "timeFrom": null,
7389 "timeRegions": [],
7390 "timeShift": null,
7391 "title": "Queued incoming RDATA commands, by stream",
7392 "tooltip": {
7393 "shared": false,
7394 "sort": 0,
7395 "value_type": "individual"
7396 },
7397 "type": "graph",
7398 "xaxis": {
7399 "buckets": null,
7400 "mode": "time",
7401 "name": null,
7402 "show": true,
7403 "values": []
7404 },
7405 "yaxes": [
7406 {
7407 "format": "short",
7408 "label": null,
7409 "logBase": 1,
7410 "max": null,
7411 "min": null,
7412 "show": true
7413 },
7414 {
7415 "format": "short",
7416 "label": null,
7417 "logBase": 1,
7418 "max": null,
7419 "min": null,
7420 "show": true
7421 }
7422 ],
7423 "yaxis": {
7424 "align": false,
7425 "alignLevel": null
7426 }
7427 },
7428 {
7429 "aliasColors": {},
7430 "bars": false,
7431 "dashLength": 10,
7432 "dashes": false,
7433 "datasource": "$datasource",
7434 "fieldConfig": {
7435 "defaults": {
7436 "custom": {},
7437 "links": []
7438 },
7439 "overrides": []
7440 },
7441 "fill": 1,
7442 "fillGradient": 0,
7443 "gridPos": {
7444 "h": 7,
7445 "w": 12,
7446 "x": 0,
7447 "y": 20
7448 },
7449 "hiddenSeries": false,
7450 "id": 43,
7451 "legend": {
7452 "avg": false,
7453 "current": false,
7454 "max": false,
7455 "min": false,
7456 "show": true,
7457 "total": false,
7458 "values": false
7459 },
7460 "lines": true,
7461 "linewidth": 1,
7462 "links": [],
7463 "nullPointMode": "null",
7464 "options": {
7465 "alertThreshold": true
7466 },
7467 "paceLength": 10,
7468 "percentage": false,
7469 "pluginVersion": "7.3.7",
7470 "pointradius": 5,
7471 "points": false,
7472 "renderer": "flot",
7473 "seriesOverrides": [],
7474 "spaceLength": 10,
7475 "stack": false,
7476 "steppedLine": false,
7477 "targets": [
7478 {
7479 "expr": "sum (rate(synapse_replication_tcp_protocol_outbound_commands{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (name, conn_id)",
7480 "format": "time_series",
7481 "intervalFactor": 2,
7482 "legendFormat": "{{job}}-{{index}} {{command}}",
7483 "refId": "A",
7484 "step": 20
7485 }
7486 ],
7487 "thresholds": [],
7488 "timeFrom": null,
7489 "timeRegions": [],
7490 "timeShift": null,
7491 "title": "Rate of outgoing commands",
7492 "tooltip": {
7493 "shared": false,
7494 "sort": 0,
7495 "value_type": "individual"
7496 },
7497 "type": "graph",
7498 "xaxis": {
7499 "buckets": null,
7500 "mode": "time",
7501 "name": null,
7502 "show": true,
7503 "values": []
7504 },
7505 "yaxes": [
7506 {
7507 "format": "hertz",
7508 "label": null,
7509 "logBase": 1,
7510 "max": null,
7511 "min": null,
7512 "show": true
7513 },
7514 {
7515 "format": "short",
7516 "label": null,
7517 "logBase": 1,
7518 "max": null,
7519 "min": null,
7520 "show": true
7521 }
7522 ],
7523 "yaxis": {
7524 "align": false,
7525 "alignLevel": null
7526 }
7527 },
7528 {
7529 "aliasColors": {},
7530 "bars": false,
7531 "dashLength": 10,
7532 "dashes": false,
7533 "datasource": "$datasource",
7534 "fieldConfig": {
7535 "defaults": {
7536 "custom": {},
7537 "links": []
7538 },
7539 "overrides": []
7540 },
7541 "fill": 1,
7542 "fillGradient": 0,
7543 "gridPos": {
7544 "h": 7,
7545 "w": 12,
7546 "x": 12,
7547 "y": 20
58897548 },
58907549 "hiddenSeries": false,
58917550 "id": 41,
59037562 "links": [],
59047563 "nullPointMode": "null",
59057564 "options": {
5906 "dataLinks": []
7565 "alertThreshold": true
59077566 },
59087567 "paceLength": 10,
59097568 "percentage": false,
7569 "pluginVersion": "7.3.7",
59107570 "pointradius": 5,
59117571 "points": false,
59127572 "renderer": "flot",
59727632 "dashLength": 10,
59737633 "dashes": false,
59747634 "datasource": "$datasource",
7635 "fieldConfig": {
7636 "defaults": {
7637 "custom": {},
7638 "links": []
7639 },
7640 "overrides": []
7641 },
59757642 "fill": 1,
59767643 "fillGradient": 0,
59777644 "gridPos": {
59787645 "h": 7,
59797646 "w": 12,
59807647 "x": 0,
5981 "y": 73
5982 },
5983 "hiddenSeries": false,
5984 "id": 42,
5985 "legend": {
5986 "avg": false,
5987 "current": false,
5988 "max": false,
5989 "min": false,
5990 "show": true,
5991 "total": false,
5992 "values": false
5993 },
5994 "lines": true,
5995 "linewidth": 1,
5996 "links": [],
5997 "nullPointMode": "null",
5998 "options": {
5999 "dataLinks": []
6000 },
6001 "paceLength": 10,
6002 "percentage": false,
6003 "pointradius": 5,
6004 "points": false,
6005 "renderer": "flot",
6006 "seriesOverrides": [],
6007 "spaceLength": 10,
6008 "stack": false,
6009 "steppedLine": false,
6010 "targets": [
6011 {
6012 "expr": "sum (rate(synapse_replication_tcp_protocol_inbound_commands{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (name, conn_id)",
6013 "format": "time_series",
6014 "intervalFactor": 2,
6015 "legendFormat": "{{job}}-{{index}} {{command}}",
6016 "refId": "A",
6017 "step": 20
6018 }
6019 ],
6020 "thresholds": [],
6021 "timeFrom": null,
6022 "timeRegions": [],
6023 "timeShift": null,
6024 "title": "Rate of incoming commands",
6025 "tooltip": {
6026 "shared": false,
6027 "sort": 0,
6028 "value_type": "individual"
6029 },
6030 "type": "graph",
6031 "xaxis": {
6032 "buckets": null,
6033 "mode": "time",
6034 "name": null,
6035 "show": true,
6036 "values": []
6037 },
6038 "yaxes": [
6039 {
6040 "format": "hertz",
6041 "label": null,
6042 "logBase": 1,
6043 "max": null,
6044 "min": null,
6045 "show": true
6046 },
6047 {
6048 "format": "short",
6049 "label": null,
6050 "logBase": 1,
6051 "max": null,
6052 "min": null,
6053 "show": true
6054 }
6055 ],
6056 "yaxis": {
6057 "align": false,
6058 "alignLevel": null
6059 }
6060 },
6061 {
6062 "aliasColors": {},
6063 "bars": false,
6064 "dashLength": 10,
6065 "dashes": false,
6066 "datasource": "$datasource",
6067 "fill": 1,
6068 "fillGradient": 0,
6069 "gridPos": {
6070 "h": 7,
6071 "w": 12,
6072 "x": 12,
6073 "y": 73
6074 },
6075 "hiddenSeries": false,
6076 "id": 43,
6077 "legend": {
6078 "avg": false,
6079 "current": false,
6080 "max": false,
6081 "min": false,
6082 "show": true,
6083 "total": false,
6084 "values": false
6085 },
6086 "lines": true,
6087 "linewidth": 1,
6088 "links": [],
6089 "nullPointMode": "null",
6090 "options": {
6091 "dataLinks": []
6092 },
6093 "paceLength": 10,
6094 "percentage": false,
6095 "pointradius": 5,
6096 "points": false,
6097 "renderer": "flot",
6098 "seriesOverrides": [],
6099 "spaceLength": 10,
6100 "stack": false,
6101 "steppedLine": false,
6102 "targets": [
6103 {
6104 "expr": "sum (rate(synapse_replication_tcp_protocol_outbound_commands{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size])) without (name, conn_id)",
6105 "format": "time_series",
6106 "intervalFactor": 2,
6107 "legendFormat": "{{job}}-{{index}} {{command}}",
6108 "refId": "A",
6109 "step": 20
6110 }
6111 ],
6112 "thresholds": [],
6113 "timeFrom": null,
6114 "timeRegions": [],
6115 "timeShift": null,
6116 "title": "Rate of outgoing commands",
6117 "tooltip": {
6118 "shared": false,
6119 "sort": 0,
6120 "value_type": "individual"
6121 },
6122 "type": "graph",
6123 "xaxis": {
6124 "buckets": null,
6125 "mode": "time",
6126 "name": null,
6127 "show": true,
6128 "values": []
6129 },
6130 "yaxes": [
6131 {
6132 "format": "hertz",
6133 "label": null,
6134 "logBase": 1,
6135 "max": null,
6136 "min": null,
6137 "show": true
6138 },
6139 {
6140 "format": "short",
6141 "label": null,
6142 "logBase": 1,
6143 "max": null,
6144 "min": null,
6145 "show": true
6146 }
6147 ],
6148 "yaxis": {
6149 "align": false,
6150 "alignLevel": null
6151 }
6152 },
6153 {
6154 "aliasColors": {},
6155 "bars": false,
6156 "dashLength": 10,
6157 "dashes": false,
6158 "datasource": "$datasource",
6159 "fill": 1,
6160 "fillGradient": 0,
6161 "gridPos": {
6162 "h": 7,
6163 "w": 12,
6164 "x": 0,
6165 "y": 80
7648 "y": 27
61667649 },
61677650 "hiddenSeries": false,
61687651 "id": 113,
61807663 "links": [],
61817664 "nullPointMode": "null",
61827665 "options": {
6183 "dataLinks": []
7666 "alertThreshold": true
61847667 },
61857668 "paceLength": 10,
61867669 "percentage": false,
7670 "pluginVersion": "7.3.7",
61877671 "pointradius": 5,
61887672 "points": false,
61897673 "renderer": "flot",
62547738 "dashLength": 10,
62557739 "dashes": false,
62567740 "datasource": "$datasource",
7741 "fieldConfig": {
7742 "defaults": {
7743 "custom": {},
7744 "links": []
7745 },
7746 "overrides": []
7747 },
62577748 "fill": 1,
62587749 "fillGradient": 0,
62597750 "gridPos": {
62607751 "h": 7,
62617752 "w": 12,
62627753 "x": 12,
6263 "y": 80
7754 "y": 27
62647755 },
62657756 "hiddenSeries": false,
62667757 "id": 115,
62787769 "links": [],
62797770 "nullPointMode": "null",
62807771 "options": {
6281 "dataLinks": []
7772 "alertThreshold": true
62827773 },
62837774 "paceLength": 10,
62847775 "percentage": false,
7776 "pluginVersion": "7.3.7",
62857777 "pointradius": 5,
62867778 "points": false,
62877779 "renderer": "flot",
63517843 "h": 1,
63527844 "w": 24,
63537845 "x": 0,
6354 "y": 39
7846 "y": 38
63557847 },
63567848 "id": 69,
63577849 "panels": [
63617853 "dashLength": 10,
63627854 "dashes": false,
63637855 "datasource": "$datasource",
7856 "fieldConfig": {
7857 "defaults": {
7858 "custom": {},
7859 "links": []
7860 },
7861 "overrides": []
7862 },
63647863 "fill": 1,
63657864 "fillGradient": 0,
63667865 "gridPos": {
63677866 "h": 9,
63687867 "w": 12,
63697868 "x": 0,
6370 "y": 40
7869 "y": 41
63717870 },
63727871 "hiddenSeries": false,
63737872 "id": 67,
63857884 "links": [],
63867885 "nullPointMode": "connected",
63877886 "options": {
6388 "dataLinks": []
7887 "alertThreshold": true
63897888 },
63907889 "paceLength": 10,
63917890 "percentage": false,
7891 "pluginVersion": "7.3.7",
63927892 "pointradius": 5,
63937893 "points": false,
63947894 "renderer": "flot",
63987898 "steppedLine": false,
63997899 "targets": [
64007900 {
6401 "expr": "max(synapse_event_persisted_position{instance=\"$instance\"}) - ignoring(instance,index, job, name) group_right() synapse_event_processing_positions{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
7901 "expr": "max(synapse_event_persisted_position{instance=\"$instance\"}) - on() group_right() synapse_event_processing_positions{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}",
64027902 "format": "time_series",
64037903 "interval": "",
64047904 "intervalFactor": 1,
64307930 "label": "events",
64317931 "logBase": 1,
64327932 "max": null,
6433 "min": null,
7933 "min": "0",
64347934 "show": true
64357935 },
64367936 {
64537953 "dashLength": 10,
64547954 "dashes": false,
64557955 "datasource": "$datasource",
7956 "fieldConfig": {
7957 "defaults": {
7958 "custom": {},
7959 "links": []
7960 },
7961 "overrides": []
7962 },
64567963 "fill": 1,
64577964 "fillGradient": 0,
64587965 "gridPos": {
64597966 "h": 9,
64607967 "w": 12,
64617968 "x": 12,
6462 "y": 40
7969 "y": 41
64637970 },
64647971 "hiddenSeries": false,
64657972 "id": 71,
64777984 "links": [],
64787985 "nullPointMode": "connected",
64797986 "options": {
6480 "dataLinks": []
7987 "alertThreshold": true
64817988 },
64827989 "paceLength": 10,
64837990 "percentage": false,
7991 "pluginVersion": "7.3.7",
64847992 "pointradius": 5,
64857993 "points": false,
64867994 "renderer": "flot",
65238031 "label": null,
65248032 "logBase": 1,
65258033 "max": null,
6526 "min": null,
8034 "min": "0",
65278035 "show": true
65288036 },
65298037 {
65468054 "dashLength": 10,
65478055 "dashes": false,
65488056 "datasource": "$datasource",
8057 "fieldConfig": {
8058 "defaults": {
8059 "custom": {},
8060 "links": []
8061 },
8062 "overrides": []
8063 },
65498064 "fill": 1,
65508065 "fillGradient": 0,
65518066 "gridPos": {
65528067 "h": 9,
65538068 "w": 12,
65548069 "x": 0,
6555 "y": 49
8070 "y": 50
65568071 },
65578072 "hiddenSeries": false,
65588073 "id": 121,
65718086 "links": [],
65728087 "nullPointMode": "connected",
65738088 "options": {
6574 "dataLinks": []
8089 "alertThreshold": true
65758090 },
65768091 "paceLength": 10,
65778092 "percentage": false,
8093 "pluginVersion": "7.3.7",
65788094 "pointradius": 5,
65798095 "points": false,
65808096 "renderer": "flot",
66468162 "h": 1,
66478163 "w": 24,
66488164 "x": 0,
6649 "y": 40
8165 "y": 39
66508166 },
66518167 "id": 126,
66528168 "panels": [
66678183 "dataFormat": "tsbuckets",
66688184 "datasource": "$datasource",
66698185 "description": "Colour reflects the number of rooms with the given number of forward extremities, or fewer.\n\nThis is only updated once an hour.",
8186 "fieldConfig": {
8187 "defaults": {
8188 "custom": {}
8189 },
8190 "overrides": []
8191 },
66708192 "gridPos": {
66718193 "h": 8,
66728194 "w": 12,
66738195 "x": 0,
6674 "y": 86
8196 "y": 42
66758197 },
66768198 "heatmap": {},
66778199 "hideZeroBuckets": true,
67248246 "dashes": false,
67258247 "datasource": "$datasource",
67268248 "description": "Number of rooms with the given number of forward extremities or fewer.\n\nThis is only updated once an hour.",
8249 "fieldConfig": {
8250 "defaults": {
8251 "custom": {},
8252 "links": []
8253 },
8254 "overrides": []
8255 },
67278256 "fill": 0,
67288257 "fillGradient": 0,
67298258 "gridPos": {
67308259 "h": 8,
67318260 "w": 12,
67328261 "x": 12,
6733 "y": 86
8262 "y": 42
67348263 },
67358264 "hiddenSeries": false,
67368265 "id": 124,
67478276 "lines": true,
67488277 "linewidth": 1,
67498278 "links": [],
6750 "nullPointMode": "null",
6751 "options": {
6752 "dataLinks": []
6753 },
8279 "nullPointMode": "connected",
67548280 "percentage": false,
8281 "pluginVersion": "7.1.3",
67558282 "pointradius": 2,
67568283 "points": false,
67578284 "renderer": "flot",
67628289 "targets": [
67638290 {
67648291 "expr": "synapse_forward_extremities_bucket{instance=\"$instance\"} > 0",
6765 "format": "time_series",
8292 "format": "heatmap",
67668293 "interval": "",
67678294 "intervalFactor": 1,
67688295 "legendFormat": "{{le}}",
67758302 "timeShift": null,
67768303 "title": "Room counts, by number of extremities",
67778304 "tooltip": {
6778 "shared": false,
6779 "sort": 1,
8305 "shared": true,
8306 "sort": 2,
67808307 "value_type": "individual"
67818308 },
67828309 "type": "graph",
67928319 "decimals": null,
67938320 "format": "none",
67948321 "label": "Number of rooms",
6795 "logBase": 1,
8322 "logBase": 10,
67968323 "max": null,
67978324 "min": null,
67988325 "show": true
68278354 "dataFormat": "tsbuckets",
68288355 "datasource": "$datasource",
68298356 "description": "Colour reflects the number of events persisted to rooms with the given number of forward extremities, or fewer.",
8357 "fieldConfig": {
8358 "defaults": {
8359 "custom": {}
8360 },
8361 "overrides": []
8362 },
68308363 "gridPos": {
68318364 "h": 8,
68328365 "w": 12,
68338366 "x": 0,
6834 "y": 94
8367 "y": 50
68358368 },
68368369 "heatmap": {},
68378370 "hideZeroBuckets": true,
68848417 "dashes": false,
68858418 "datasource": "$datasource",
68868419 "description": "For a given percentage P, the number X where P% of events were persisted to rooms with X forward extremities or fewer.",
8420 "fieldConfig": {
8421 "defaults": {
8422 "custom": {},
8423 "links": []
8424 },
8425 "overrides": []
8426 },
68878427 "fill": 1,
68888428 "fillGradient": 0,
68898429 "gridPos": {
68908430 "h": 8,
68918431 "w": 12,
68928432 "x": 12,
6893 "y": 94
8433 "y": 50
68948434 },
68958435 "hiddenSeries": false,
68968436 "id": 128,
69078447 "linewidth": 1,
69088448 "links": [],
69098449 "nullPointMode": "null",
6910 "options": {
6911 "dataLinks": []
6912 },
69138450 "percentage": false,
8451 "pluginVersion": "7.1.3",
69148452 "pointradius": 2,
69158453 "points": false,
69168454 "renderer": "flot",
70058543 "dataFormat": "tsbuckets",
70068544 "datasource": "$datasource",
70078545 "description": "Colour reflects the number of events persisted to rooms with the given number of stale forward extremities, or fewer.\n\nStale forward extremities are those that were in the previous set of extremities as well as the new.",
8546 "fieldConfig": {
8547 "defaults": {
8548 "custom": {}
8549 },
8550 "overrides": []
8551 },
70088552 "gridPos": {
70098553 "h": 8,
70108554 "w": 12,
70118555 "x": 0,
7012 "y": 102
8556 "y": 58
70138557 },
70148558 "heatmap": {},
70158559 "hideZeroBuckets": true,
70628606 "dashes": false,
70638607 "datasource": "$datasource",
70648608 "description": "For given percentage P, the number X where P% of events were persisted to rooms with X stale forward extremities or fewer.\n\nStale forward extremities are those that were in the previous set of extremities as well as the new.",
8609 "fieldConfig": {
8610 "defaults": {
8611 "custom": {},
8612 "links": []
8613 },
8614 "overrides": []
8615 },
70658616 "fill": 1,
70668617 "fillGradient": 0,
70678618 "gridPos": {
70688619 "h": 8,
70698620 "w": 12,
70708621 "x": 12,
7071 "y": 102
8622 "y": 58
70728623 },
70738624 "hiddenSeries": false,
70748625 "id": 130,
70858636 "linewidth": 1,
70868637 "links": [],
70878638 "nullPointMode": "null",
7088 "options": {
7089 "dataLinks": []
7090 },
70918639 "percentage": false,
8640 "pluginVersion": "7.1.3",
70928641 "pointradius": 2,
70938642 "points": false,
70948643 "renderer": "flot",
71838732 "dataFormat": "tsbuckets",
71848733 "datasource": "$datasource",
71858734 "description": "Colour reflects the number of state resolution operations performed over the given number of state groups, or fewer.",
8735 "fieldConfig": {
8736 "defaults": {
8737 "custom": {}
8738 },
8739 "overrides": []
8740 },
71868741 "gridPos": {
71878742 "h": 8,
71888743 "w": 12,
71898744 "x": 0,
7190 "y": 110
8745 "y": 66
71918746 },
71928747 "heatmap": {},
71938748 "hideZeroBuckets": true,
72418796 "dashes": false,
72428797 "datasource": "$datasource",
72438798 "description": "For a given percentage P, the number X where P% of state resolution operations took place over X state groups or fewer.",
8799 "fieldConfig": {
8800 "defaults": {
8801 "custom": {},
8802 "links": []
8803 },
8804 "overrides": []
8805 },
72448806 "fill": 1,
72458807 "fillGradient": 0,
72468808 "gridPos": {
72478809 "h": 8,
72488810 "w": 12,
72498811 "x": 12,
7250 "y": 110
8812 "y": 66
72518813 },
72528814 "hiddenSeries": false,
72538815 "id": 132,
72658827 "linewidth": 1,
72668828 "links": [],
72678829 "nullPointMode": "null",
7268 "options": {
7269 "dataLinks": []
7270 },
72718830 "percentage": false,
8831 "pluginVersion": "7.1.3",
72728832 "pointradius": 2,
72738833 "points": false,
72748834 "renderer": "flot",
73508910 "align": false,
73518911 "alignLevel": null
73528912 }
8913 },
8914 {
8915 "aliasColors": {},
8916 "bars": false,
8917 "dashLength": 10,
8918 "dashes": false,
8919 "datasource": "$datasource",
8920 "description": "When we do a state res while persisting events we try and see if we can prune any stale extremities.",
8921 "fieldConfig": {
8922 "defaults": {
8923 "custom": {}
8924 },
8925 "overrides": []
8926 },
8927 "fill": 1,
8928 "fillGradient": 0,
8929 "gridPos": {
8930 "h": 8,
8931 "w": 12,
8932 "x": 0,
8933 "y": 74
8934 },
8935 "hiddenSeries": false,
8936 "id": 179,
8937 "legend": {
8938 "avg": false,
8939 "current": false,
8940 "max": false,
8941 "min": false,
8942 "show": true,
8943 "total": false,
8944 "values": false
8945 },
8946 "lines": true,
8947 "linewidth": 1,
8948 "nullPointMode": "null",
8949 "percentage": false,
8950 "pluginVersion": "7.1.3",
8951 "pointradius": 2,
8952 "points": false,
8953 "renderer": "flot",
8954 "seriesOverrides": [],
8955 "spaceLength": 10,
8956 "stack": false,
8957 "steppedLine": false,
8958 "targets": [
8959 {
8960 "expr": "sum(rate(synapse_storage_events_state_resolutions_during_persistence{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size]))",
8961 "interval": "",
8962 "legendFormat": "State res ",
8963 "refId": "A"
8964 },
8965 {
8966 "expr": "sum(rate(synapse_storage_events_potential_times_prune_extremities{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size]))",
8967 "interval": "",
8968 "legendFormat": "Potential to prune",
8969 "refId": "B"
8970 },
8971 {
8972 "expr": "sum(rate(synapse_storage_events_times_pruned_extremities{instance=\"$instance\",job=~\"$job\",index=~\"$index\"}[$bucket_size]))",
8973 "interval": "",
8974 "legendFormat": "Pruned",
8975 "refId": "C"
8976 }
8977 ],
8978 "thresholds": [],
8979 "timeFrom": null,
8980 "timeRegions": [],
8981 "timeShift": null,
8982 "title": "Stale extremity dropping",
8983 "tooltip": {
8984 "shared": true,
8985 "sort": 0,
8986 "value_type": "individual"
8987 },
8988 "type": "graph",
8989 "xaxis": {
8990 "buckets": null,
8991 "mode": "time",
8992 "name": null,
8993 "show": true,
8994 "values": []
8995 },
8996 "yaxes": [
8997 {
8998 "format": "hertz",
8999 "label": null,
9000 "logBase": 1,
9001 "max": null,
9002 "min": null,
9003 "show": true
9004 },
9005 {
9006 "format": "short",
9007 "label": null,
9008 "logBase": 1,
9009 "max": null,
9010 "min": null,
9011 "show": true
9012 }
9013 ],
9014 "yaxis": {
9015 "align": false,
9016 "alignLevel": null
9017 }
73539018 }
73549019 ],
73559020 "title": "Extremities",
73569021 "type": "row"
9022 },
9023 {
9024 "collapsed": true,
9025 "datasource": "${DS_PROMETHEUS}",
9026 "gridPos": {
9027 "h": 1,
9028 "w": 24,
9029 "x": 0,
9030 "y": 40
9031 },
9032 "id": 158,
9033 "panels": [
9034 {
9035 "aliasColors": {},
9036 "bars": false,
9037 "dashLength": 10,
9038 "dashes": false,
9039 "datasource": "$datasource",
9040 "fieldConfig": {
9041 "defaults": {
9042 "custom": {},
9043 "links": []
9044 },
9045 "overrides": []
9046 },
9047 "fill": 1,
9048 "fillGradient": 0,
9049 "gridPos": {
9050 "h": 8,
9051 "w": 12,
9052 "x": 0,
9053 "y": 119
9054 },
9055 "hiddenSeries": false,
9056 "id": 156,
9057 "legend": {
9058 "avg": false,
9059 "current": false,
9060 "max": false,
9061 "min": false,
9062 "show": true,
9063 "total": false,
9064 "values": false
9065 },
9066 "lines": true,
9067 "linewidth": 1,
9068 "links": [],
9069 "nullPointMode": "null",
9070 "options": {
9071 "alertThreshold": true
9072 },
9073 "percentage": false,
9074 "pluginVersion": "7.3.7",
9075 "pointradius": 5,
9076 "points": false,
9077 "renderer": "flot",
9078 "seriesOverrides": [
9079 {
9080 "alias": "Max",
9081 "color": "#bf1b00",
9082 "fill": 0,
9083 "linewidth": 2
9084 }
9085 ],
9086 "spaceLength": 10,
9087 "stack": false,
9088 "steppedLine": false,
9089 "targets": [
9090 {
9091 "expr": "synapse_admin_mau:current{instance=\"$instance\"}",
9092 "format": "time_series",
9093 "interval": "",
9094 "intervalFactor": 1,
9095 "legendFormat": "Current",
9096 "refId": "A"
9097 },
9098 {
9099 "expr": "synapse_admin_mau:max{instance=\"$instance\"}",
9100 "format": "time_series",
9101 "interval": "",
9102 "intervalFactor": 1,
9103 "legendFormat": "Max",
9104 "refId": "B"
9105 }
9106 ],
9107 "thresholds": [],
9108 "timeFrom": null,
9109 "timeRegions": [],
9110 "timeShift": null,
9111 "title": "MAU Limits",
9112 "tooltip": {
9113 "shared": true,
9114 "sort": 0,
9115 "value_type": "individual"
9116 },
9117 "type": "graph",
9118 "xaxis": {
9119 "buckets": null,
9120 "mode": "time",
9121 "name": null,
9122 "show": true,
9123 "values": []
9124 },
9125 "yaxes": [
9126 {
9127 "format": "short",
9128 "label": null,
9129 "logBase": 1,
9130 "max": null,
9131 "min": "0",
9132 "show": true
9133 },
9134 {
9135 "format": "short",
9136 "label": null,
9137 "logBase": 1,
9138 "max": null,
9139 "min": null,
9140 "show": true
9141 }
9142 ],
9143 "yaxis": {
9144 "align": false,
9145 "alignLevel": null
9146 }
9147 },
9148 {
9149 "aliasColors": {},
9150 "bars": false,
9151 "dashLength": 10,
9152 "dashes": false,
9153 "datasource": "$datasource",
9154 "fieldConfig": {
9155 "defaults": {
9156 "custom": {}
9157 },
9158 "overrides": []
9159 },
9160 "fill": 1,
9161 "fillGradient": 0,
9162 "gridPos": {
9163 "h": 8,
9164 "w": 12,
9165 "x": 12,
9166 "y": 119
9167 },
9168 "hiddenSeries": false,
9169 "id": 160,
9170 "legend": {
9171 "avg": false,
9172 "current": false,
9173 "max": false,
9174 "min": false,
9175 "show": true,
9176 "total": false,
9177 "values": false
9178 },
9179 "lines": true,
9180 "linewidth": 1,
9181 "nullPointMode": "null",
9182 "options": {
9183 "alertThreshold": true
9184 },
9185 "percentage": false,
9186 "pluginVersion": "7.3.7",
9187 "pointradius": 2,
9188 "points": false,
9189 "renderer": "flot",
9190 "seriesOverrides": [],
9191 "spaceLength": 10,
9192 "stack": false,
9193 "steppedLine": false,
9194 "targets": [
9195 {
9196 "expr": "synapse_admin_mau_current_mau_by_service{instance=\"$instance\"}",
9197 "interval": "",
9198 "legendFormat": "{{ app_service }}",
9199 "refId": "A"
9200 }
9201 ],
9202 "thresholds": [],
9203 "timeFrom": null,
9204 "timeRegions": [],
9205 "timeShift": null,
9206 "title": "MAU by Appservice",
9207 "tooltip": {
9208 "shared": true,
9209 "sort": 0,
9210 "value_type": "individual"
9211 },
9212 "type": "graph",
9213 "xaxis": {
9214 "buckets": null,
9215 "mode": "time",
9216 "name": null,
9217 "show": true,
9218 "values": []
9219 },
9220 "yaxes": [
9221 {
9222 "format": "short",
9223 "label": null,
9224 "logBase": 1,
9225 "max": null,
9226 "min": null,
9227 "show": true
9228 },
9229 {
9230 "format": "short",
9231 "label": null,
9232 "logBase": 1,
9233 "max": null,
9234 "min": null,
9235 "show": true
9236 }
9237 ],
9238 "yaxis": {
9239 "align": false,
9240 "alignLevel": null
9241 }
9242 }
9243 ],
9244 "title": "MAU",
9245 "type": "row"
9246 },
9247 {
9248 "collapsed": true,
9249 "datasource": "${DS_PROMETHEUS}",
9250 "gridPos": {
9251 "h": 1,
9252 "w": 24,
9253 "x": 0,
9254 "y": 41
9255 },
9256 "id": 177,
9257 "panels": [
9258 {
9259 "aliasColors": {},
9260 "bars": false,
9261 "dashLength": 10,
9262 "dashes": false,
9263 "datasource": "$datasource",
9264 "fieldConfig": {
9265 "defaults": {
9266 "custom": {},
9267 "links": []
9268 },
9269 "overrides": []
9270 },
9271 "fill": 1,
9272 "fillGradient": 0,
9273 "gridPos": {
9274 "h": 7,
9275 "w": 12,
9276 "x": 0,
9277 "y": 1
9278 },
9279 "hiddenSeries": false,
9280 "id": 173,
9281 "legend": {
9282 "avg": false,
9283 "current": false,
9284 "max": false,
9285 "min": false,
9286 "show": true,
9287 "total": false,
9288 "values": false
9289 },
9290 "lines": true,
9291 "linewidth": 1,
9292 "links": [],
9293 "nullPointMode": "null",
9294 "percentage": false,
9295 "pluginVersion": "7.1.3",
9296 "pointradius": 5,
9297 "points": false,
9298 "renderer": "flot",
9299 "seriesOverrides": [],
9300 "spaceLength": 10,
9301 "stack": false,
9302 "steppedLine": false,
9303 "targets": [
9304 {
9305 "expr": "rate(synapse_notifier_users_woken_by_stream{job=\"$job\",index=~\"$index\",instance=\"$instance\"}[$bucket_size])",
9306 "format": "time_series",
9307 "hide": false,
9308 "intervalFactor": 2,
9309 "legendFormat": "{{stream}} {{index}}",
9310 "metric": "synapse_notifier",
9311 "refId": "A",
9312 "step": 2
9313 }
9314 ],
9315 "thresholds": [],
9316 "timeFrom": null,
9317 "timeRegions": [],
9318 "timeShift": null,
9319 "title": "Notifier Streams Woken",
9320 "tooltip": {
9321 "shared": true,
9322 "sort": 2,
9323 "value_type": "individual"
9324 },
9325 "type": "graph",
9326 "xaxis": {
9327 "buckets": null,
9328 "mode": "time",
9329 "name": null,
9330 "show": true,
9331 "values": []
9332 },
9333 "yaxes": [
9334 {
9335 "format": "hertz",
9336 "label": null,
9337 "logBase": 1,
9338 "max": null,
9339 "min": null,
9340 "show": true
9341 },
9342 {
9343 "format": "short",
9344 "label": null,
9345 "logBase": 1,
9346 "max": null,
9347 "min": null,
9348 "show": true
9349 }
9350 ],
9351 "yaxis": {
9352 "align": false,
9353 "alignLevel": null
9354 }
9355 },
9356 {
9357 "aliasColors": {},
9358 "bars": false,
9359 "dashLength": 10,
9360 "dashes": false,
9361 "datasource": "$datasource",
9362 "fieldConfig": {
9363 "defaults": {
9364 "custom": {},
9365 "links": []
9366 },
9367 "overrides": []
9368 },
9369 "fill": 1,
9370 "fillGradient": 0,
9371 "gridPos": {
9372 "h": 7,
9373 "w": 12,
9374 "x": 12,
9375 "y": 1
9376 },
9377 "hiddenSeries": false,
9378 "id": 175,
9379 "legend": {
9380 "avg": false,
9381 "current": false,
9382 "max": false,
9383 "min": false,
9384 "show": true,
9385 "total": false,
9386 "values": false
9387 },
9388 "lines": true,
9389 "linewidth": 1,
9390 "links": [],
9391 "nullPointMode": "null",
9392 "percentage": false,
9393 "pluginVersion": "7.1.3",
9394 "pointradius": 5,
9395 "points": false,
9396 "renderer": "flot",
9397 "seriesOverrides": [],
9398 "spaceLength": 10,
9399 "stack": false,
9400 "steppedLine": false,
9401 "targets": [
9402 {
9403 "expr": "rate(synapse_handler_presence_get_updates{job=~\"$job\",instance=\"$instance\"}[$bucket_size])",
9404 "format": "time_series",
9405 "interval": "",
9406 "intervalFactor": 2,
9407 "legendFormat": "{{type}} {{index}}",
9408 "refId": "A",
9409 "step": 2
9410 }
9411 ],
9412 "thresholds": [],
9413 "timeFrom": null,
9414 "timeRegions": [],
9415 "timeShift": null,
9416 "title": "Presence Stream Fetch Type Rates",
9417 "tooltip": {
9418 "shared": true,
9419 "sort": 2,
9420 "value_type": "individual"
9421 },
9422 "type": "graph",
9423 "xaxis": {
9424 "buckets": null,
9425 "mode": "time",
9426 "name": null,
9427 "show": true,
9428 "values": []
9429 },
9430 "yaxes": [
9431 {
9432 "format": "hertz",
9433 "label": null,
9434 "logBase": 1,
9435 "max": null,
9436 "min": "0",
9437 "show": true
9438 },
9439 {
9440 "format": "short",
9441 "label": null,
9442 "logBase": 1,
9443 "max": null,
9444 "min": null,
9445 "show": true
9446 }
9447 ],
9448 "yaxis": {
9449 "align": false,
9450 "alignLevel": null
9451 }
9452 }
9453 ],
9454 "title": "Notifier",
9455 "type": "row"
9456 },
9457 {
9458 "collapsed": true,
9459 "datasource": "${DS_PROMETHEUS}",
9460 "gridPos": {
9461 "h": 1,
9462 "w": 24,
9463 "x": 0,
9464 "y": 42
9465 },
9466 "id": 170,
9467 "panels": [
9468 {
9469 "aliasColors": {},
9470 "bars": false,
9471 "dashLength": 10,
9472 "dashes": false,
9473 "datasource": "$datasource",
9474 "fieldConfig": {
9475 "defaults": {
9476 "custom": {}
9477 },
9478 "overrides": []
9479 },
9480 "fill": 1,
9481 "fillGradient": 0,
9482 "gridPos": {
9483 "h": 8,
9484 "w": 12,
9485 "x": 0,
9486 "y": 73
9487 },
9488 "hiddenSeries": false,
9489 "id": 168,
9490 "legend": {
9491 "avg": false,
9492 "current": false,
9493 "max": false,
9494 "min": false,
9495 "show": true,
9496 "total": false,
9497 "values": false
9498 },
9499 "lines": true,
9500 "linewidth": 1,
9501 "nullPointMode": "null",
9502 "options": {
9503 "alertThreshold": true
9504 },
9505 "percentage": false,
9506 "pluginVersion": "7.3.7",
9507 "pointradius": 2,
9508 "points": false,
9509 "renderer": "flot",
9510 "seriesOverrides": [],
9511 "spaceLength": 10,
9512 "stack": false,
9513 "steppedLine": false,
9514 "targets": [
9515 {
9516 "expr": "rate(synapse_appservice_api_sent_events{instance=\"$instance\"}[$bucket_size])",
9517 "interval": "",
9518 "legendFormat": "{{exported_service}}",
9519 "refId": "A"
9520 }
9521 ],
9522 "thresholds": [],
9523 "timeFrom": null,
9524 "timeRegions": [],
9525 "timeShift": null,
9526 "title": "Sent Events rate",
9527 "tooltip": {
9528 "shared": true,
9529 "sort": 0,
9530 "value_type": "individual"
9531 },
9532 "type": "graph",
9533 "xaxis": {
9534 "buckets": null,
9535 "mode": "time",
9536 "name": null,
9537 "show": true,
9538 "values": []
9539 },
9540 "yaxes": [
9541 {
9542 "format": "hertz",
9543 "label": null,
9544 "logBase": 1,
9545 "max": null,
9546 "min": null,
9547 "show": true
9548 },
9549 {
9550 "format": "short",
9551 "label": null,
9552 "logBase": 1,
9553 "max": null,
9554 "min": null,
9555 "show": true
9556 }
9557 ],
9558 "yaxis": {
9559 "align": false,
9560 "alignLevel": null
9561 }
9562 },
9563 {
9564 "aliasColors": {},
9565 "bars": false,
9566 "dashLength": 10,
9567 "dashes": false,
9568 "datasource": "$datasource",
9569 "fieldConfig": {
9570 "defaults": {
9571 "custom": {}
9572 },
9573 "overrides": []
9574 },
9575 "fill": 1,
9576 "fillGradient": 0,
9577 "gridPos": {
9578 "h": 8,
9579 "w": 12,
9580 "x": 12,
9581 "y": 73
9582 },
9583 "hiddenSeries": false,
9584 "id": 171,
9585 "legend": {
9586 "avg": false,
9587 "current": false,
9588 "max": false,
9589 "min": false,
9590 "show": true,
9591 "total": false,
9592 "values": false
9593 },
9594 "lines": true,
9595 "linewidth": 1,
9596 "nullPointMode": "null",
9597 "options": {
9598 "alertThreshold": true
9599 },
9600 "percentage": false,
9601 "pluginVersion": "7.3.7",
9602 "pointradius": 2,
9603 "points": false,
9604 "renderer": "flot",
9605 "seriesOverrides": [],
9606 "spaceLength": 10,
9607 "stack": false,
9608 "steppedLine": false,
9609 "targets": [
9610 {
9611 "expr": "rate(synapse_appservice_api_sent_transactions{instance=\"$instance\"}[$bucket_size])",
9612 "interval": "",
9613 "legendFormat": "{{exported_service}}",
9614 "refId": "A"
9615 }
9616 ],
9617 "thresholds": [],
9618 "timeFrom": null,
9619 "timeRegions": [],
9620 "timeShift": null,
9621 "title": "Transactions rate",
9622 "tooltip": {
9623 "shared": true,
9624 "sort": 0,
9625 "value_type": "individual"
9626 },
9627 "type": "graph",
9628 "xaxis": {
9629 "buckets": null,
9630 "mode": "time",
9631 "name": null,
9632 "show": true,
9633 "values": []
9634 },
9635 "yaxes": [
9636 {
9637 "format": "hertz",
9638 "label": null,
9639 "logBase": 1,
9640 "max": null,
9641 "min": null,
9642 "show": true
9643 },
9644 {
9645 "format": "short",
9646 "label": null,
9647 "logBase": 1,
9648 "max": null,
9649 "min": null,
9650 "show": true
9651 }
9652 ],
9653 "yaxis": {
9654 "align": false,
9655 "alignLevel": null
9656 }
9657 }
9658 ],
9659 "title": "Appservices",
9660 "type": "row"
9661 },
9662 {
9663 "collapsed": true,
9664 "datasource": "${DS_PROMETHEUS}",
9665 "gridPos": {
9666 "h": 1,
9667 "w": 24,
9668 "x": 0,
9669 "y": 43
9670 },
9671 "id": 188,
9672 "panels": [
9673 {
9674 "aliasColors": {},
9675 "bars": false,
9676 "dashLength": 10,
9677 "dashes": false,
9678 "datasource": "$datasource",
9679 "fieldConfig": {
9680 "defaults": {
9681 "custom": {}
9682 },
9683 "overrides": []
9684 },
9685 "fill": 1,
9686 "fillGradient": 0,
9687 "gridPos": {
9688 "h": 8,
9689 "w": 12,
9690 "x": 0,
9691 "y": 44
9692 },
9693 "hiddenSeries": false,
9694 "id": 182,
9695 "legend": {
9696 "avg": false,
9697 "current": false,
9698 "max": false,
9699 "min": false,
9700 "show": true,
9701 "total": false,
9702 "values": false
9703 },
9704 "lines": true,
9705 "linewidth": 1,
9706 "nullPointMode": "null",
9707 "options": {
9708 "alertThreshold": true
9709 },
9710 "percentage": false,
9711 "pluginVersion": "7.3.7",
9712 "pointradius": 2,
9713 "points": false,
9714 "renderer": "flot",
9715 "seriesOverrides": [],
9716 "spaceLength": 10,
9717 "stack": false,
9718 "steppedLine": false,
9719 "targets": [
9720 {
9721 "expr": "rate(synapse_handler_presence_notified_presence{job=\"$job\",index=~\"$index\",instance=\"$instance\"}[$bucket_size])",
9722 "interval": "",
9723 "legendFormat": "Notified",
9724 "refId": "A"
9725 },
9726 {
9727 "expr": "rate(synapse_handler_presence_federation_presence_out{job=\"$job\",index=~\"$index\",instance=\"$instance\"}[$bucket_size])",
9728 "interval": "",
9729 "legendFormat": "Remote ping",
9730 "refId": "B"
9731 },
9732 {
9733 "expr": "rate(synapse_handler_presence_presence_updates{job=\"$job\",index=~\"$index\",instance=\"$instance\"}[$bucket_size])",
9734 "interval": "",
9735 "legendFormat": "Total updates",
9736 "refId": "C"
9737 },
9738 {
9739 "expr": "rate(synapse_handler_presence_federation_presence{job=\"$job\",index=~\"$index\",instance=\"$instance\"}[$bucket_size])",
9740 "interval": "",
9741 "legendFormat": "Remote updates",
9742 "refId": "D"
9743 },
9744 {
9745 "expr": "rate(synapse_handler_presence_bump_active_time{job=\"$job\",index=~\"$index\",instance=\"$instance\"}[$bucket_size])",
9746 "interval": "",
9747 "legendFormat": "Bump active time",
9748 "refId": "E"
9749 }
9750 ],
9751 "thresholds": [],
9752 "timeFrom": null,
9753 "timeRegions": [],
9754 "timeShift": null,
9755 "title": "Presence",
9756 "tooltip": {
9757 "shared": true,
9758 "sort": 2,
9759 "value_type": "individual"
9760 },
9761 "type": "graph",
9762 "xaxis": {
9763 "buckets": null,
9764 "mode": "time",
9765 "name": null,
9766 "show": true,
9767 "values": []
9768 },
9769 "yaxes": [
9770 {
9771 "format": "hertz",
9772 "label": null,
9773 "logBase": 1,
9774 "max": null,
9775 "min": null,
9776 "show": true
9777 },
9778 {
9779 "format": "short",
9780 "label": null,
9781 "logBase": 1,
9782 "max": null,
9783 "min": null,
9784 "show": true
9785 }
9786 ],
9787 "yaxis": {
9788 "align": false,
9789 "alignLevel": null
9790 }
9791 },
9792 {
9793 "aliasColors": {},
9794 "bars": false,
9795 "dashLength": 10,
9796 "dashes": false,
9797 "datasource": "$datasource",
9798 "fieldConfig": {
9799 "defaults": {
9800 "custom": {}
9801 },
9802 "overrides": []
9803 },
9804 "fill": 1,
9805 "fillGradient": 0,
9806 "gridPos": {
9807 "h": 8,
9808 "w": 12,
9809 "x": 12,
9810 "y": 44
9811 },
9812 "hiddenSeries": false,
9813 "id": 184,
9814 "legend": {
9815 "avg": false,
9816 "current": false,
9817 "max": false,
9818 "min": false,
9819 "show": true,
9820 "total": false,
9821 "values": false
9822 },
9823 "lines": true,
9824 "linewidth": 1,
9825 "nullPointMode": "null",
9826 "options": {
9827 "alertThreshold": true
9828 },
9829 "percentage": false,
9830 "pluginVersion": "7.3.7",
9831 "pointradius": 2,
9832 "points": false,
9833 "renderer": "flot",
9834 "seriesOverrides": [],
9835 "spaceLength": 10,
9836 "stack": false,
9837 "steppedLine": false,
9838 "targets": [
9839 {
9840 "expr": "rate(synapse_handler_presence_state_transition{job=\"$job\",index=~\"$index\",instance=\"$instance\"}[$bucket_size])",
9841 "interval": "",
9842 "legendFormat": "{{from}} -> {{to}}",
9843 "refId": "A"
9844 }
9845 ],
9846 "thresholds": [],
9847 "timeFrom": null,
9848 "timeRegions": [],
9849 "timeShift": null,
9850 "title": "Presence state transitions",
9851 "tooltip": {
9852 "shared": true,
9853 "sort": 2,
9854 "value_type": "individual"
9855 },
9856 "type": "graph",
9857 "xaxis": {
9858 "buckets": null,
9859 "mode": "time",
9860 "name": null,
9861 "show": true,
9862 "values": []
9863 },
9864 "yaxes": [
9865 {
9866 "format": "hertz",
9867 "label": null,
9868 "logBase": 1,
9869 "max": null,
9870 "min": null,
9871 "show": true
9872 },
9873 {
9874 "format": "short",
9875 "label": null,
9876 "logBase": 1,
9877 "max": null,
9878 "min": null,
9879 "show": true
9880 }
9881 ],
9882 "yaxis": {
9883 "align": false,
9884 "alignLevel": null
9885 }
9886 },
9887 {
9888 "aliasColors": {},
9889 "bars": false,
9890 "dashLength": 10,
9891 "dashes": false,
9892 "datasource": "$datasource",
9893 "fieldConfig": {
9894 "defaults": {
9895 "custom": {}
9896 },
9897 "overrides": []
9898 },
9899 "fill": 1,
9900 "fillGradient": 0,
9901 "gridPos": {
9902 "h": 8,
9903 "w": 12,
9904 "x": 0,
9905 "y": 52
9906 },
9907 "hiddenSeries": false,
9908 "id": 186,
9909 "legend": {
9910 "avg": false,
9911 "current": false,
9912 "max": false,
9913 "min": false,
9914 "show": true,
9915 "total": false,
9916 "values": false
9917 },
9918 "lines": true,
9919 "linewidth": 1,
9920 "nullPointMode": "null",
9921 "options": {
9922 "alertThreshold": true
9923 },
9924 "percentage": false,
9925 "pluginVersion": "7.3.7",
9926 "pointradius": 2,
9927 "points": false,
9928 "renderer": "flot",
9929 "seriesOverrides": [],
9930 "spaceLength": 10,
9931 "stack": false,
9932 "steppedLine": false,
9933 "targets": [
9934 {
9935 "expr": "rate(synapse_handler_presence_notify_reason{job=\"$job\",index=~\"$index\",instance=\"$instance\"}[$bucket_size])",
9936 "interval": "",
9937 "legendFormat": "{{reason}}",
9938 "refId": "A"
9939 }
9940 ],
9941 "thresholds": [],
9942 "timeFrom": null,
9943 "timeRegions": [],
9944 "timeShift": null,
9945 "title": "Presence notify reason",
9946 "tooltip": {
9947 "shared": true,
9948 "sort": 2,
9949 "value_type": "individual"
9950 },
9951 "type": "graph",
9952 "xaxis": {
9953 "buckets": null,
9954 "mode": "time",
9955 "name": null,
9956 "show": true,
9957 "values": []
9958 },
9959 "yaxes": [
9960 {
9961 "$$hashKey": "object:165",
9962 "format": "hertz",
9963 "label": null,
9964 "logBase": 1,
9965 "max": null,
9966 "min": null,
9967 "show": true
9968 },
9969 {
9970 "$$hashKey": "object:166",
9971 "format": "short",
9972 "label": null,
9973 "logBase": 1,
9974 "max": null,
9975 "min": null,
9976 "show": true
9977 }
9978 ],
9979 "yaxis": {
9980 "align": false,
9981 "alignLevel": null
9982 }
9983 }
9984 ],
9985 "title": "Presence",
9986 "type": "row"
9987 },
9988 {
9989 "collapsed": true,
9990 "datasource": "${DS_PROMETHEUS}",
9991 "gridPos": {
9992 "h": 1,
9993 "w": 24,
9994 "x": 0,
9995 "y": 44
9996 },
9997 "id": 197,
9998 "panels": [
9999 {
10000 "aliasColors": {},
10001 "bars": false,
10002 "dashLength": 10,
10003 "dashes": false,
10004 "datasource": "$datasource",
10005 "fieldConfig": {
10006 "defaults": {
10007 "custom": {}
10008 },
10009 "overrides": []
10010 },
10011 "fill": 1,
10012 "fillGradient": 0,
10013 "gridPos": {
10014 "h": 8,
10015 "w": 12,
10016 "x": 0,
10017 "y": 1
10018 },
10019 "hiddenSeries": false,
10020 "id": 191,
10021 "legend": {
10022 "avg": false,
10023 "current": false,
10024 "max": false,
10025 "min": false,
10026 "show": true,
10027 "total": false,
10028 "values": false
10029 },
10030 "lines": true,
10031 "linewidth": 1,
10032 "nullPointMode": "null",
10033 "options": {
10034 "alertThreshold": true
10035 },
10036 "percentage": false,
10037 "pluginVersion": "7.3.7",
10038 "pointradius": 2,
10039 "points": false,
10040 "renderer": "flot",
10041 "seriesOverrides": [],
10042 "spaceLength": 10,
10043 "stack": false,
10044 "steppedLine": false,
10045 "targets": [
10046 {
10047 "expr": "rate(synapse_external_cache_set{job=\"$job\", instance=\"$instance\", index=~\"$index\"}[$bucket_size])",
10048 "interval": "",
10049 "legendFormat": "{{ cache_name }} {{ index }}",
10050 "refId": "A"
10051 }
10052 ],
10053 "thresholds": [],
10054 "timeFrom": null,
10055 "timeRegions": [],
10056 "timeShift": null,
10057 "title": "External Cache Set Rate",
10058 "tooltip": {
10059 "shared": true,
10060 "sort": 2,
10061 "value_type": "individual"
10062 },
10063 "type": "graph",
10064 "xaxis": {
10065 "buckets": null,
10066 "mode": "time",
10067 "name": null,
10068 "show": true,
10069 "values": []
10070 },
10071 "yaxes": [
10072 {
10073 "$$hashKey": "object:390",
10074 "format": "hertz",
10075 "label": null,
10076 "logBase": 1,
10077 "max": null,
10078 "min": null,
10079 "show": true
10080 },
10081 {
10082 "$$hashKey": "object:391",
10083 "format": "short",
10084 "label": null,
10085 "logBase": 1,
10086 "max": null,
10087 "min": null,
10088 "show": true
10089 }
10090 ],
10091 "yaxis": {
10092 "align": false,
10093 "alignLevel": null
10094 }
10095 },
10096 {
10097 "aliasColors": {},
10098 "bars": false,
10099 "dashLength": 10,
10100 "dashes": false,
10101 "datasource": "$datasource",
10102 "description": "",
10103 "fieldConfig": {
10104 "defaults": {
10105 "custom": {}
10106 },
10107 "overrides": []
10108 },
10109 "fill": 1,
10110 "fillGradient": 0,
10111 "gridPos": {
10112 "h": 8,
10113 "w": 12,
10114 "x": 12,
10115 "y": 1
10116 },
10117 "hiddenSeries": false,
10118 "id": 193,
10119 "legend": {
10120 "avg": false,
10121 "current": false,
10122 "max": false,
10123 "min": false,
10124 "show": true,
10125 "total": false,
10126 "values": false
10127 },
10128 "lines": true,
10129 "linewidth": 1,
10130 "nullPointMode": "null",
10131 "options": {
10132 "alertThreshold": true
10133 },
10134 "percentage": false,
10135 "pluginVersion": "7.3.7",
10136 "pointradius": 2,
10137 "points": false,
10138 "renderer": "flot",
10139 "seriesOverrides": [],
10140 "spaceLength": 10,
10141 "stack": false,
10142 "steppedLine": false,
10143 "targets": [
10144 {
10145 "expr": "rate(synapse_external_cache_get{job=\"$job\", instance=\"$instance\", index=~\"$index\"}[$bucket_size])",
10146 "interval": "",
10147 "legendFormat": "{{ cache_name }} {{ index }}",
10148 "refId": "A"
10149 }
10150 ],
10151 "thresholds": [],
10152 "timeFrom": null,
10153 "timeRegions": [],
10154 "timeShift": null,
10155 "title": "External Cache Get Rate",
10156 "tooltip": {
10157 "shared": true,
10158 "sort": 2,
10159 "value_type": "individual"
10160 },
10161 "type": "graph",
10162 "xaxis": {
10163 "buckets": null,
10164 "mode": "time",
10165 "name": null,
10166 "show": true,
10167 "values": []
10168 },
10169 "yaxes": [
10170 {
10171 "$$hashKey": "object:390",
10172 "format": "hertz",
10173 "label": null,
10174 "logBase": 1,
10175 "max": null,
10176 "min": null,
10177 "show": true
10178 },
10179 {
10180 "$$hashKey": "object:391",
10181 "format": "short",
10182 "label": null,
10183 "logBase": 1,
10184 "max": null,
10185 "min": null,
10186 "show": true
10187 }
10188 ],
10189 "yaxis": {
10190 "align": false,
10191 "alignLevel": null
10192 }
10193 },
10194 {
10195 "cards": {
10196 "cardPadding": -1,
10197 "cardRound": null
10198 },
10199 "color": {
10200 "cardColor": "#b4ff00",
10201 "colorScale": "sqrt",
10202 "colorScheme": "interpolateInferno",
10203 "exponent": 0.5,
10204 "min": 0,
10205 "mode": "spectrum"
10206 },
10207 "dataFormat": "tsbuckets",
10208 "datasource": "$datasource",
10209 "fieldConfig": {
10210 "defaults": {
10211 "custom": {}
10212 },
10213 "overrides": []
10214 },
10215 "gridPos": {
10216 "h": 9,
10217 "w": 12,
10218 "x": 0,
10219 "y": 9
10220 },
10221 "heatmap": {},
10222 "hideZeroBuckets": false,
10223 "highlightCards": true,
10224 "id": 195,
10225 "legend": {
10226 "show": false
10227 },
10228 "links": [],
10229 "reverseYBuckets": false,
10230 "targets": [
10231 {
10232 "expr": "sum(rate(synapse_external_cache_response_time_seconds_bucket{index=~\"$index\",instance=\"$instance\",job=\"$job\"}[$bucket_size])) by (le)",
10233 "format": "heatmap",
10234 "instant": false,
10235 "interval": "",
10236 "intervalFactor": 1,
10237 "legendFormat": "{{le}}",
10238 "refId": "A"
10239 }
10240 ],
10241 "title": "External Cache Response Time",
10242 "tooltip": {
10243 "show": true,
10244 "showHistogram": true
10245 },
10246 "tooltipDecimals": 2,
10247 "type": "heatmap",
10248 "xAxis": {
10249 "show": true
10250 },
10251 "xBucketNumber": null,
10252 "xBucketSize": null,
10253 "yAxis": {
10254 "decimals": 0,
10255 "format": "s",
10256 "logBase": 1,
10257 "max": null,
10258 "min": null,
10259 "show": true,
10260 "splitFactor": null
10261 },
10262 "yBucketBound": "auto",
10263 "yBucketNumber": null,
10264 "yBucketSize": null
10265 }
10266 ],
10267 "title": "External Cache",
10268 "type": "row"
735710269 }
735810270 ],
7359 "refresh": "5m",
7360 "schemaVersion": 22,
10271 "refresh": false,
10272 "schemaVersion": 26,
736110273 "style": "dark",
736210274 "tags": [
736310275 "matrix"
736710279 {
736810280 "current": {
736910281 "selected": false,
7370 "text": "Prometheus",
7371 "value": "Prometheus"
10282 "text": "default",
10283 "value": "default"
737210284 },
10285 "error": null,
737310286 "hide": 0,
737410287 "includeAll": false,
737510288 "label": null,
737710290 "name": "datasource",
737810291 "options": [],
737910292 "query": "prometheus",
10293 "queryValue": "",
738010294 "refresh": 1,
738110295 "regex": "",
738210296 "skipUrlSync": false,
738610300 "allFormat": "glob",
738710301 "auto": true,
738810302 "auto_count": 100,
7389 "auto_min": "30s",
10303 "auto_min": "60s",
739010304 "current": {
739110305 "selected": false,
739210306 "text": "auto",
739310307 "value": "$__auto_interval_bucket_size"
739410308 },
739510309 "datasource": null,
10310 "error": null,
739610311 "hide": 0,
739710312 "includeAll": false,
739810313 "label": "Bucket Size",
743710352 }
743810353 ],
743910354 "query": "30s,1m,2m,5m,10m,15m",
10355 "queryValue": "",
744010356 "refresh": 2,
744110357 "skipUrlSync": false,
744210358 "type": "interval"
744610362 "current": {},
744710363 "datasource": "$datasource",
744810364 "definition": "",
10365 "error": null,
744910366 "hide": 0,
745010367 "includeAll": false,
7451 "index": -1,
745210368 "label": null,
745310369 "multi": false,
745410370 "name": "instance",
745710373 "refresh": 2,
745810374 "regex": "",
745910375 "skipUrlSync": false,
7460 "sort": 0,
10376 "sort": 1,
746110377 "tagValuesQuery": "",
746210378 "tags": [],
746310379 "tagsQuery": "",
747010386 "current": {},
747110387 "datasource": "$datasource",
747210388 "definition": "",
10389 "error": null,
747310390 "hide": 0,
747410391 "hideLabel": false,
747510392 "includeAll": true,
7476 "index": -1,
747710393 "label": "Job",
747810394 "multi": true,
747910395 "multiFormat": "regex values",
749710413 "current": {},
749810414 "datasource": "$datasource",
749910415 "definition": "",
10416 "error": null,
750010417 "hide": 0,
750110418 "hideLabel": false,
750210419 "includeAll": true,
7503 "index": -1,
750410420 "label": "",
750510421 "multi": true,
750610422 "multiFormat": "regex values",
752110437 ]
752210438 },
752310439 "time": {
7524 "from": "now-1h",
10440 "from": "now-3h",
752510441 "to": "now"
752610442 },
752710443 "timepicker": {
755310469 "timezone": "",
755410470 "title": "Synapse",
755510471 "uid": "000000012",
7556 "variables": {
7557 "list": []
7558 },
7559 "version": 32
10472 "version": 90
756010473 }⏎
0 [Service]
1 # The following directives give the synapse service R/W access to:
2 # - /run/matrix-synapse
3 # - /var/lib/matrix-synapse
4 # - /var/log/matrix-synapse
5
6 RuntimeDirectory=matrix-synapse
7 StateDirectory=matrix-synapse
8 LogsDirectory=matrix-synapse
9
10 ######################
11 ## Security Sandbox ##
12 ######################
13
14 # Make sure that the service has its own unshared tmpfs at /tmp and that it
15 # cannot see or change any real devices
16 PrivateTmp=true
17 PrivateDevices=true
18
19 # We give no capabilities to a service by default
20 CapabilityBoundingSet=
21 AmbientCapabilities=
22
23 # Protect the following from modification:
24 # - The entire filesystem
25 # - sysctl settings and loaded kernel modules
26 # - No modifications allowed to Control Groups
27 # - Hostname
28 # - System Clock
29 ProtectSystem=strict
30 ProtectKernelTunables=true
31 ProtectKernelModules=true
32 ProtectControlGroups=true
33 ProtectClock=true
34 ProtectHostname=true
35
36 # Prevent access to the following:
37 # - /home directory
38 # - Kernel logs
39 ProtectHome=tmpfs
40 ProtectKernelLogs=true
41
42 # Make sure that the process can only see PIDs and process details of itself,
43 # and the second option disables seeing details of things like system load and
44 # I/O etc
45 ProtectProc=invisible
46 ProcSubset=pid
47
48 # While not needed, we set these options explicitly
49 # - This process has been given access to the host network
50 # - It can also communicate with any IP Address
51 PrivateNetwork=false
52 RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
53 IPAddressAllow=any
54
55 # Restrict system calls to a sane bunch
56 SystemCallArchitectures=native
57 SystemCallFilter=@system-service
58 SystemCallFilter=~@privileged @resources @obsolete
59
60 # Misc restrictions
61 # - Since the process is a python process it needs to be able to write and
62 # execute memory regions, so we set MemoryDenyWriteExecute to false
63 RestrictSUIDSGID=true
64 RemoveIPC=true
65 NoNewPrivileges=true
66 RestrictRealtime=true
67 RestrictNamespaces=true
68 LockPersonality=true
69 PrivateUsers=true
70 MemoryDenyWriteExecute=false
0 matrix-synapse-py3 (1.35.0) stable; urgency=medium
1
2 * New synapse release 1.35.0.
3
4 -- Synapse Packaging team <packages@matrix.org> Tue, 01 Jun 2021 13:23:35 +0100
5
06 matrix-synapse-py3 (1.34.0) stable; urgency=medium
17
28 * New synapse release 1.34.0.
88 {% endif %}
99
1010 handlers:
11 {% if LOG_FILE_PATH %}
1112 file:
1213 class: logging.handlers.TimedRotatingFileHandler
1314 formatter: precise
14 filename: {{ LOG_FILE_PATH or "homeserver.log" }}
15 filename: {{ LOG_FILE_PATH }}
1516 when: "midnight"
1617 backupCount: 6 # Does not include the current log file.
1718 encoding: utf8
2829 # be written to disk.
2930 capacity: 10
3031 flushLevel: 30 # Flush for WARNING logs as well
32 {% endif %}
3133
3234 console:
3335 class: logging.StreamHandler
183183 """
184184
185185 NGINX_LOCATION_CONFIG_BLOCK = """
186 location ~* {endpoint} {
186 location ~* {endpoint} {{
187187 proxy_pass {upstream};
188188 proxy_set_header X-Forwarded-For $remote_addr;
189189 proxy_set_header X-Forwarded-Proto $scheme;
190190 proxy_set_header Host $host;
191 }
191 }}
192192 """
193193
194194 NGINX_UPSTREAM_CONFIG_BLOCK = """
195 upstream {upstream_worker_type} {
195 upstream {upstream_worker_type} {{
196196 {body}
197 }
197 }}
198198 """
199199
200200
33 * [Usage](#usage)
44 - [Room Details API](#room-details-api)
55 - [Room Members API](#room-members-api)
6 - [Room State API](#room-state-api)
67 - [Delete Room API](#delete-room-api)
78 * [Parameters](#parameters-1)
89 * [Response](#response)
4141 using docker like so:
4242
4343 ```sh
44 docker run -d --name jaeger
44 docker run -d --name jaeger \
4545 -p 6831:6831/udp \
4646 -p 6832:6832/udp \
4747 -p 5778:5778 \
4848 -p 16686:16686 \
4949 -p 14268:14268 \
50 jaegertracing/all-in-one:1.13
50 jaegertracing/all-in-one:1
5151 ```
5252
5353 Latest documentation is probably at
54 <https://www.jaegertracing.io/docs/1.13/getting-started/>
54 https://www.jaegertracing.io/docs/latest/getting-started.
5555
5656 ## Enable OpenTracing in Synapse
5757
6161
6262 ```yaml
6363 opentracing:
64 tracer_enabled: true
64 enabled: true
6565 homeserver_whitelist:
6666 - "mytrustedhomeserver.org"
6767 - "*.myotherhomeservers.com"
8989 ## Configuring Jaeger
9090
9191 Sampling strategies can be set as in this document:
92 <https://www.jaegertracing.io/docs/1.13/sampling/>
92 <https://www.jaegertracing.io/docs/latest/sampling/>.
00 # Using Postgres
11
2 Postgres version 9.5 or later is known to work.
2 Synapse supports PostgreSQL versions 9.6 or later.
33
44 ## Install postgres client libraries
55
3232 # Or, if your system uses sudo to get administrative rights
3333 sudo -u postgres bash
3434
35 Then, create a user ``synapse_user`` with:
36
35 Then, create a postgres user and a database with:
36
37 # this will prompt for a password for the new user
3738 createuser --pwprompt synapse_user
3839
39 Before you can authenticate with the `synapse_user`, you must create a
40 database that it can access. To create a database, first connect to the
41 database with your database user:
42
43 su - postgres # Or: sudo -u postgres bash
44 psql
45
46 and then run:
47
48 CREATE DATABASE synapse
49 ENCODING 'UTF8'
50 LC_COLLATE='C'
51 LC_CTYPE='C'
52 template=template0
53 OWNER synapse_user;
54
55 This would create an appropriate database named `synapse` owned by the
56 `synapse_user` user (which must already have been created as above).
40 createdb --encoding=UTF8 --locale=C --template=template0 --owner=synapse_user synapse
41
42 The above will create a user called `synapse_user`, and a database called
43 `synapse`.
5744
5845 Note that the PostgreSQL database *must* have the correct encoding set
5946 (as shown above), otherwise it will not be able to store UTF8 strings.
6148 You may need to enable password authentication so `synapse_user` can
6249 connect to the database. See
6350 <https://www.postgresql.org/docs/current/auth-pg-hba-conf.html>.
64
65 If you get an error along the lines of `FATAL: Ident authentication failed for
66 user "synapse_user"`, you may need to use an authentication method other than
67 `ident`:
68
69 * If the `synapse_user` user has a password, add the password to the `database:`
70 section of `homeserver.yaml`. Then add the following to `pg_hba.conf`:
71
72 ```
73 host synapse synapse_user ::1/128 md5 # or `scram-sha-256` instead of `md5` if you use that
74 ```
75
76 * If the `synapse_user` user does not have a password, then a password doesn't
77 have to be added to `homeserver.yaml`. But the following does need to be added
78 to `pg_hba.conf`:
79
80 ```
81 host synapse synapse_user ::1/128 trust
82 ```
83
84 Note that line order matters in `pg_hba.conf`, so make sure that if you do add a
85 new line, it is inserted before:
86
87 ```
88 host all all ::1/128 ident
89 ```
90
91 ### Fixing incorrect `COLLATE` or `CTYPE`
92
93 Synapse will refuse to set up a new database if it has the wrong values of
94 `COLLATE` and `CTYPE` set, and will log warnings on existing databases. Using
95 different locales can cause issues if the locale library is updated from
96 underneath the database, or if a different version of the locale is used on any
97 replicas.
98
99 The safest way to fix the issue is to take a dump and recreate the database with
100 the correct `COLLATE` and `CTYPE` parameters (as shown above). It is also possible to change the
101 parameters on a live database and run a `REINDEX` on the entire database,
102 however extreme care must be taken to avoid database corruption.
103
104 Note that the above may fail with an error about duplicate rows if corruption
105 has already occurred, and such duplicate rows will need to be manually removed.
106
107
108 ## Fixing inconsistent sequences error
109
110 Synapse uses Postgres sequences to generate IDs for various tables. A sequence
111 and associated table can get out of sync if, for example, Synapse has been
112 downgraded and then upgraded again.
113
114 To fix the issue shut down Synapse (including any and all workers) and run the
115 SQL command included in the error message. Once done Synapse should start
116 successfully.
117
118
119 ## Tuning Postgres
120
121 The default settings should be fine for most deployments. For larger
122 scale deployments tuning some of the settings is recommended, details of
123 which can be found at
124 <https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server>.
125
126 In particular, we've found tuning the following values helpful for
127 performance:
128
129 - `shared_buffers`
130 - `effective_cache_size`
131 - `work_mem`
132 - `maintenance_work_mem`
133 - `autovacuum_work_mem`
134
135 Note that the appropriate values for those fields depend on the amount
136 of free memory the database host has available.
13751
13852 ## Synapse config
13953
16478 database server. Example values might be:
16579
16680 ```yaml
167 # seconds of inactivity after which TCP should send a keepalive message to the server
168 keepalives_idle: 10
169
170 # the number of seconds after which a TCP keepalive message that is not
171 # acknowledged by the server should be retransmitted
172 keepalives_interval: 10
173
174 # the number of TCP keepalives that can be lost before the client's connection
175 # to the server is considered dead
176 keepalives_count: 3
177 ```
81 database:
82 args:
83 # ... as above
84
85 # seconds of inactivity after which TCP should send a keepalive message to the server
86 keepalives_idle: 10
87
88 # the number of seconds after which a TCP keepalive message that is not
89 # acknowledged by the server should be retransmitted
90 keepalives_interval: 10
91
92 # the number of TCP keepalives that can be lost before the client's connection
93 # to the server is considered dead
94 keepalives_count: 3
95 ```
96
97 ## Tuning Postgres
98
99 The default settings should be fine for most deployments. For larger
100 scale deployments tuning some of the settings is recommended, details of
101 which can be found at
102 <https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server>.
103
104 In particular, we've found tuning the following values helpful for
105 performance:
106
107 - `shared_buffers`
108 - `effective_cache_size`
109 - `work_mem`
110 - `maintenance_work_mem`
111 - `autovacuum_work_mem`
112
113 Note that the appropriate values for those fields depend on the amount
114 of free memory the database host has available.
115
178116
179117 ## Porting from SQLite
180118
184122 backed by SQLite to using PostgreSQL. This is done in as a two phase
185123 process:
186124
187 1. Copy the existing SQLite database to a separate location (while the
188 server is down) and running the port script against that offline
189 database.
125 1. Copy the existing SQLite database to a separate location and run
126 the port script against that offline database.
190127 2. Shut down the server. Rerun the port script to port any data that
191128 has come in since taking the first snapshot. Restart server against
192129 the PostgreSQL database.
244181 ./synctl start
245182
246183 Synapse should now be running against PostgreSQL.
184
185
186 ## Troubleshooting
187
188 ### Alternative auth methods
189
190 If you get an error along the lines of `FATAL: Ident authentication failed for
191 user "synapse_user"`, you may need to use an authentication method other than
192 `ident`:
193
194 * If the `synapse_user` user has a password, add the password to the `database:`
195 section of `homeserver.yaml`. Then add the following to `pg_hba.conf`:
196
197 ```
198 host synapse synapse_user ::1/128 md5 # or `scram-sha-256` instead of `md5` if you use that
199 ```
200
201 * If the `synapse_user` user does not have a password, then a password doesn't
202 have to be added to `homeserver.yaml`. But the following does need to be added
203 to `pg_hba.conf`:
204
205 ```
206 host synapse synapse_user ::1/128 trust
207 ```
208
209 Note that line order matters in `pg_hba.conf`, so make sure that if you do add a
210 new line, it is inserted before:
211
212 ```
213 host all all ::1/128 ident
214 ```
215
216 ### Fixing incorrect `COLLATE` or `CTYPE`
217
218 Synapse will refuse to set up a new database if it has the wrong values of
219 `COLLATE` and `CTYPE` set, and will log warnings on existing databases. Using
220 different locales can cause issues if the locale library is updated from
221 underneath the database, or if a different version of the locale is used on any
222 replicas.
223
224 The safest way to fix the issue is to dump the database and recreate it with
225 the correct locale parameter (as shown above). It is also possible to change the
226 parameters on a live database and run a `REINDEX` on the entire database,
227 however extreme care must be taken to avoid database corruption.
228
229 Note that the above may fail with an error about duplicate rows if corruption
230 has already occurred, and such duplicate rows will need to be manually removed.
231
232 ### Fixing inconsistent sequences error
233
234 Synapse uses Postgres sequences to generate IDs for various tables. A sequence
235 and associated table can get out of sync if, for example, Synapse has been
236 downgraded and then upgraded again.
237
238 To fix the issue shut down Synapse (including any and all workers) and run the
239 SQL command included in the error message. Once done Synapse should start
240 successfully.
2727 which can be given a list of local or remote MXIDs to broadcast known, online user
2828 presence to (for those users that the receiving user is considered interested in).
2929 It does not include state for users who are currently offline, and it can only be
30 called on workers that support sending federation.
30 called on workers that support sending federation. Additionally, this method must
31 only be called from the process that has been configured to write to the
32 the [presence stream](https://github.com/matrix-org/synapse/blob/master/docs/workers.md#stream-writers).
33 By default, this is the main process, but another worker can be configured to do
34 so.
3135
3236 ### Module structure
3337
681681 # If unspecified, we will use CONFDIR/client.key.
682682 #
683683 account_key_file: DATADIR/acme_account.key
684
685 # List of allowed TLS fingerprints for this server to publish along
686 # with the signing keys for this server. Other matrix servers that
687 # make HTTPS requests to this server will check that the TLS
688 # certificates returned by this server match one of the fingerprints.
689 #
690 # Synapse automatically adds the fingerprint of its own certificate
691 # to the list. So if federation traffic is handled directly by synapse
692 # then no modification to the list is required.
693 #
694 # If synapse is run behind a load balancer that handles the TLS then it
695 # will be necessary to add the fingerprints of the certificates used by
696 # the loadbalancers to this list if they are different to the one
697 # synapse is using.
698 #
699 # Homeservers are permitted to cache the list of TLS fingerprints
700 # returned in the key responses up to the "valid_until_ts" returned in
701 # key. It may be necessary to publish the fingerprints of a new
702 # certificate and wait until the "valid_until_ts" of the previous key
703 # responses have passed before deploying it.
704 #
705 # You can calculate a fingerprint from a given TLS listener via:
706 # openssl s_client -connect $host:$port < /dev/null 2> /dev/null |
707 # openssl x509 -outform DER | openssl sha256 -binary | base64 | tr -d '='
708 # or by checking matrix.org/federationtester/api/report?server_name=$host
709 #
710 #tls_fingerprints: [{"sha256": "<base64_encoded_sha256_fingerprint>"}]
711684
712685
713686 ## Federation ##
28442817 #enabled: true
28452818
28462819 # The list of homeservers we wish to send and receive span contexts and span baggage.
2847 # See docs/opentracing.rst
2820 # See docs/opentracing.rst.
2821 #
28482822 # This is a list of regexes which are matched against the server_name of the
28492823 # homeserver.
28502824 #
28532827 #homeserver_whitelist:
28542828 # - ".*"
28552829
2830 # A list of the matrix IDs of users whose requests will always be traced,
2831 # even if the tracing system would otherwise drop the traces due to
2832 # probabilistic sampling.
2833 #
2834 # By default, the list is empty.
2835 #
2836 #force_tracing_for_users:
2837 # - "@user1:server_name"
2838 # - "@user2:server_name"
2839
28562840 # Jaeger can be configured to sample traces at different rates.
28572841 # All configuration options provided by Jaeger can be set here.
2858 # Jaeger's configuration mostly related to trace sampling which
2842 # Jaeger's configuration is mostly related to trace sampling which
28592843 # is documented here:
2860 # https://www.jaegertracing.io/docs/1.13/sampling/.
2844 # https://www.jaegertracing.io/docs/latest/sampling/.
28612845 #
28622846 #jaeger_config:
28632847 # sampler:
28642848 # type: const
28652849 # param: 1
2866
2867 # Logging whether spans were started and reported
2868 #
28692850 # logging:
28702851 # false
28712852
29342915 # Optional password if configured on the Redis instance
29352916 #
29362917 #password: <secret_password>
2918
2919
2920 # Enable experimental features in Synapse.
2921 #
2922 # Experimental features might break or be removed without a deprecation
2923 # period.
2924 #
2925 experimental_features:
2926 # Support for Spaces (MSC1772), it enables the following:
2927 #
2928 # * The Spaces Summary API (MSC2946).
2929 # * Restricting room membership based on space membership (MSC3083).
2930 #
2931 # Uncomment to disable support for Spaces.
2932 #spaces_enabled: false
6666 - Arguments:
6767 - `userinfo` - A `authlib.oidc.core.claims.UserInfo` object to extract user
6868 information from.
69 - This method must return a string, which is the unique identifier for the
70 user. Commonly the ``sub`` claim of the response.
69 - This method must return a string, which is the unique, immutable identifier
70 for the user. Commonly the `sub` claim of the response.
7171 * `map_user_attributes(self, userinfo, token, failures)`
7272 - This method must be async.
7373 - Arguments:
8686 `localpart` value, such as `john.doe1`.
8787 - Returns a dictionary with two keys:
8888 - `localpart`: A string, used to generate the Matrix ID. If this is
89 `None`, the user is prompted to pick their own username.
89 `None`, the user is prompted to pick their own username. This is only used
90 during a user's first login. Once a localpart has been associated with a
91 remote user ID (see `get_remote_user_id`) it cannot be updated.
9092 - `displayname`: An optional string, the display name for the user.
9193 * `get_extra_attributes(self, userinfo, token)`
9294 - This method must be async.
152154 information from.
153155 - `client_redirect_url` - A string, the URL that the client will be
154156 redirected to.
155 - This method must return a string, which is the unique identifier for the
156 user. Commonly the ``uid`` claim of the response.
157 - This method must return a string, which is the unique, immutable identifier
158 for the user. Commonly the `uid` claim of the response.
157159 * `saml_response_to_user_attributes(self, saml_response, failures, client_redirect_url)`
158160 - Arguments:
159161 - `saml_response` - A `saml2.response.AuthnResponse` object to extract user
171173 redirected to.
172174 - This method must return a dictionary, which will then be used by Synapse
173175 to build a new user. The following keys are allowed:
174 * `mxid_localpart` - The mxid localpart of the new user. If this is
175 `None`, the user is prompted to pick their own username.
176 * `mxid_localpart` - A string, the mxid localpart of the new user. If this is
177 `None`, the user is prompted to pick their own username. This is only used
178 during a user's first login. Once a localpart has been associated with a
179 remote user ID (see `get_remote_user_id`) it cannot be updated.
176180 * `displayname` - The displayname of the new user. If not provided, will default to
177181 the value of `mxid_localpart`.
178182 * `emails` - A list of emails for the new user. If not provided, will
6464 systemctl enable matrix-synapse-worker@federation_writer.service
6565 systemctl restart matrix-synapse.target
6666 ```
67
68 ## Hardening
69
70 **Optional:** If further hardening is desired, the file
71 `override-hardened.conf` may be copied from
72 `contrib/systemd/override-hardened.conf` in this repository to the location
73 `/etc/systemd/system/matrix-synapse.service.d/override-hardened.conf` (the
74 directory may have to be created). It enables certain sandboxing features in
75 systemd to further secure the synapse service. You may read the comments to
76 understand what the override file is doing. The same file will need to be copied
77 to
78 `/etc/systemd/system/matrix-synapse-worker@.service.d/override-hardened-worker.conf`
79 (this directory may also have to be created) in order to apply the same
80 hardening options to any worker processes.
81
82 Once these files have been copied to their appropriate locations, simply reload
83 systemd's manager config files and restart all Synapse services to apply the hardening options. They will automatically
84 be applied at every restart as long as the override files are present at the
85 specified locations.
86
87 ```sh
88 systemctl daemon-reload
89
90 # Restart services
91 systemctl restart matrix-synapse.target
92 ```
93
94 In order to see their effect, you may run `systemd-analyze security
95 matrix-synapse.service` before and after applying the hardening options to see
96 the changes being applied at a glance.
66
77 The directory info is stored in various tables, which can (typically after
88 DB corruption) get stale or out of sync. If this happens, for now the
9 solution to fix it is to execute the SQL [here](../synapse/storage/databases/main/schema/delta/53/user_dir_populate.sql)
9 solution to fix it is to execute the SQL [here](https://github.com/matrix-org/synapse/blob/master/synapse/storage/schema/main/delta/53/user_dir_populate.sql)
1010 and then restart synapse. This should then start a background task to
1111 flush the current tables and regenerate the directory.
7070 synapse/types.py,
7171 synapse/util/async_helpers.py,
7272 synapse/util/caches,
73 synapse/util/daemonize.py,
74 synapse/util/hash.py,
75 synapse/util/iterutils.py,
7376 synapse/util/metrics.py,
7477 synapse/util/macaroons.py,
78 synapse/util/module_loader.py,
79 synapse/util/msisdn.py,
7580 synapse/util/stringutils.py,
7681 synapse/visibility.py,
7782 tests/replication,
7984 tests/handlers/test_password_providers.py,
8085 tests/rest/client/v1/test_login.py,
8186 tests/rest/client/v2_alpha/test_auth.py,
87 tests/util/test_itertools.py,
8288 tests/util/test_stream_change_cache.py
8389
8490 [mypy-pymacaroons.*]
173179
174180 [mypy-pympler.*]
175181 ignore_missing_imports = True
182
183 [mypy-phonenumbers.*]
184 ignore_missing_imports = True
185
186 [mypy-ijson.*]
187 ignore_missing_imports = True
2929 def format_plain(public_key: nacl.signing.VerifyKey):
3030 print(
3131 "%s:%s %s"
32 % (public_key.alg, public_key.version, encode_verify_key_base64(public_key),)
32 % (
33 public_key.alg,
34 public_key.version,
35 encode_verify_key_base64(public_key),
36 )
3337 )
3438
3539
4953 parser = argparse.ArgumentParser()
5054
5155 parser.add_argument(
52 "key_file", nargs="+", type=argparse.FileType("r"), help="The key file to read",
56 "key_file",
57 nargs="+",
58 type=argparse.FileType("r"),
59 help="The key file to read",
5360 )
5461
5562 parser.add_argument(
6269 parser.add_argument(
6370 "--expiry-ts",
6471 type=int,
65 default=int(time.time() * 1000) + 6*3600000,
72 default=int(time.time() * 1000) + 6 * 3600000,
6673 help=(
6774 "The expiry time to use for -x, in milliseconds since 1970. The default "
6875 "is (now+6h)."
1010 parser.add_argument(
1111 "--config-dir",
1212 default="CONFDIR",
13
1413 help="The path where the config files are kept. Used to create filenames for "
15 "things like the log config and the signing key. Default: %(default)s",
14 "things like the log config and the signing key. Default: %(default)s",
1615 )
1716
1817 parser.add_argument(
1918 "--data-dir",
2019 default="DATADIR",
2120 help="The path where the data files are kept. Used to create filenames for "
22 "things like the database and media store. Default: %(default)s",
21 "things like the database and media store. Default: %(default)s",
2322 )
2423
2524 parser.add_argument(
2625 "--server-name",
2726 default="SERVERNAME",
2827 help="The server name. Used to initialise the server_name config param, but also "
29 "used in the names of some of the config files. Default: %(default)s",
28 "used in the names of some of the config files. Default: %(default)s",
3029 )
3130
3231 parser.add_argument(
4039 "--generate-secrets",
4140 action="store_true",
4241 help="Enable generation of new secrets for things like the macaroon_secret_key."
43 "By default, these parameters will be left unset."
42 "By default, these parameters will be left unset.",
4443 )
4544
4645 parser.add_argument(
47 "-o", "--output-file",
48 type=argparse.FileType('w'),
46 "-o",
47 "--output-file",
48 type=argparse.FileType("w"),
4949 default=sys.stdout,
5050 help="File to write the configuration to. Default: stdout",
5151 )
5252
5353 parser.add_argument(
5454 "--header-file",
55 type=argparse.FileType('r'),
55 type=argparse.FileType("r"),
5656 help="File from which to read a header, which will be printed before the "
57 "generated config.",
57 "generated config.",
5858 )
5959
6060 args = parser.parse_args()
4040 parser.add_argument(
4141 "-c",
4242 "--config",
43 type=argparse.FileType('r'),
43 type=argparse.FileType("r"),
4444 help=(
4545 "Path to server config file. "
4646 "Used to read in bcrypt_rounds and password_pepper."
7171 pw = unicodedata.normalize("NFKC", password)
7272
7373 hashed = bcrypt.hashpw(
74 pw.encode('utf8') + password_pepper.encode("utf8"),
74 pw.encode("utf8") + password_pepper.encode("utf8"),
7575 bcrypt.gensalt(bcrypt_rounds),
76 ).decode('ascii')
76 ).decode("ascii")
7777
7878 print(hashed)
293293 return table, already_ported, total_to_port, forward_chunk, backward_chunk
294294
295295 async def get_table_constraints(self) -> Dict[str, Set[str]]:
296 """Returns a map of tables that have foreign key constraints to tables they depend on.
297 """
296 """Returns a map of tables that have foreign key constraints to tables they depend on."""
298297
299298 def _get_constraints(txn):
300299 # We can pull the information about foreign key constraints out from
503502 return
504503
505504 def build_db_store(
506 self, db_config: DatabaseConnectionConfig, allow_outdated_version: bool = False,
505 self,
506 db_config: DatabaseConnectionConfig,
507 allow_outdated_version: bool = False,
507508 ):
508509 """Builds and returns a database store using the provided configuration.
509510
739740 return col
740741
741742 outrows = []
742 for i, row in enumerate(rows):
743 for row in rows:
743744 try:
744745 outrows.append(
745746 tuple(conv(j, col) for j, col in enumerate(row) if j > 0)
889890 await self.postgres_store.db_pool.runInteraction("setup_user_id_seq", r)
890891
891892 async def _setup_events_stream_seqs(self) -> None:
892 """Set the event stream sequences to the correct values.
893 """
893 """Set the event stream sequences to the correct values."""
894894
895895 # We get called before we've ported the events table, so we need to
896896 # fetch the current positions from the SQLite store.
919919 )
920920
921921 await self.postgres_store.db_pool.runInteraction(
922 "_setup_events_stream_seqs", _setup_events_stream_seqs_set_pos,
923 )
924
925 async def _setup_sequence(self, sequence_name: str, stream_id_tables: Iterable[str]) -> None:
926 """Set a sequence to the correct value.
927 """
922 "_setup_events_stream_seqs",
923 _setup_events_stream_seqs_set_pos,
924 )
925
926 async def _setup_sequence(
927 self, sequence_name: str, stream_id_tables: Iterable[str]
928 ) -> None:
929 """Set a sequence to the correct value."""
928930 current_stream_ids = []
929931 for stream_id_table in stream_id_tables:
930932 max_stream_id = await self.sqlite_store.db_pool.simple_select_one_onecol(
938940 next_id = max(current_stream_ids) + 1
939941
940942 def r(txn):
941 sql = "ALTER SEQUENCE %s RESTART WITH" % (sequence_name, )
942 txn.execute(sql + " %s", (next_id, ))
943
944 await self.postgres_store.db_pool.runInteraction("_setup_%s" % (sequence_name,), r)
943 sql = "ALTER SEQUENCE %s RESTART WITH" % (sequence_name,)
944 txn.execute(sql + " %s", (next_id,))
945
946 await self.postgres_store.db_pool.runInteraction(
947 "_setup_%s" % (sequence_name,), r
948 )
945949
946950 async def _setup_auth_chain_sequence(self) -> None:
947951 curr_chain_id = await self.sqlite_store.db_pool.simple_select_one_onecol(
948 table="event_auth_chains", keyvalues={}, retcol="MAX(chain_id)", allow_none=True
952 table="event_auth_chains",
953 keyvalues={},
954 retcol="MAX(chain_id)",
955 allow_none=True,
949956 )
950957
951958 def r(txn):
952959 txn.execute(
953960 "ALTER SEQUENCE event_auth_chain_id RESTART WITH %s",
954 (curr_chain_id,),
961 (curr_chain_id + 1,),
955962 )
956963
957964 if curr_chain_id is not None:
967974
968975
969976 class Progress(object):
970 """Used to report progress of the port
971 """
977 """Used to report progress of the port"""
972978
973979 def __init__(self):
974980 self.tables = {}
993999
9941000
9951001 class CursesProgress(Progress):
996 """Reports progress to a curses window
997 """
1002 """Reports progress to a curses window"""
9981003
9991004 def __init__(self, stdscr):
10001005 self.stdscr = stdscr
10191024
10201025 self.total_processed = 0
10211026 self.total_remaining = 0
1022 for table, data in self.tables.items():
1027 for data in self.tables.values():
10231028 self.total_processed += data["num_done"] - data["start"]
10241029 self.total_remaining += data["total"] - data["num_done"]
10251030
11101115
11111116
11121117 class TerminalProgress(Progress):
1113 """Just prints progress to the terminal
1114 """
1118 """Just prints progress to the terminal"""
11151119
11161120 def update(self, table, num_done):
11171121 super(TerminalProgress, self).update(table, num_done)
2020 "debian:buster",
2121 "debian:bullseye",
2222 "debian:sid",
23 "ubuntu:bionic", # 18.04 LTS (our EOL forced by Py36 on 2021-12-23)
24 "ubuntu:focal", # 20.04 LTS (our EOL forced by Py38 on 2024-10-14)
25 "ubuntu:groovy", # 20.10 (EOL 2021-07-07)
23 "ubuntu:bionic", # 18.04 LTS (our EOL forced by Py36 on 2021-12-23)
24 "ubuntu:focal", # 20.04 LTS (our EOL forced by Py38 on 2024-10-14)
25 "ubuntu:groovy", # 20.10 (EOL 2021-07-07)
2626 "ubuntu:hirsute", # 21.04 (EOL 2022-01-05)
2727 )
2828
29 DESC = '''\
29 DESC = """\
3030 Builds .debs for synapse, using a Docker image for the build environment.
3131
3232 By default, builds for all known distributions, but a list of distributions
3333 can be passed on the commandline for debugging.
34 '''
34 """
3535
3636
3737 class Builder(object):
4545 """Build deb for a single distribution"""
4646
4747 if self._failed:
48 print("not building %s due to earlier failure" % (dist, ))
48 print("not building %s due to earlier failure" % (dist,))
4949 raise Exception("failed")
5050
5151 try:
6767 # we tend to get source packages which are full of debs. (We could hack
6868 # around that with more magic in the build_debian.sh script, but that
6969 # doesn't solve the problem for natively-run dpkg-buildpakage).
70 debsdir = os.path.join(projdir, '../debs')
70 debsdir = os.path.join(projdir, "../debs")
7171 os.makedirs(debsdir, exist_ok=True)
7272
7373 if self.redirect_stdout:
74 logfile = os.path.join(debsdir, "%s.buildlog" % (tag, ))
74 logfile = os.path.join(debsdir, "%s.buildlog" % (tag,))
7575 print("building %s: directing output to %s" % (dist, logfile))
7676 stdout = open(logfile, "w")
7777 else:
7878 stdout = None
7979
8080 # first build a docker image for the build environment
81 subprocess.check_call([
82 "docker", "build",
83 "--tag", "dh-venv-builder:" + tag,
84 "--build-arg", "distro=" + dist,
85 "-f", "docker/Dockerfile-dhvirtualenv",
86 "docker",
87 ], stdout=stdout, stderr=subprocess.STDOUT)
81 subprocess.check_call(
82 [
83 "docker",
84 "build",
85 "--tag",
86 "dh-venv-builder:" + tag,
87 "--build-arg",
88 "distro=" + dist,
89 "-f",
90 "docker/Dockerfile-dhvirtualenv",
91 "docker",
92 ],
93 stdout=stdout,
94 stderr=subprocess.STDOUT,
95 )
8896
8997 container_name = "synapse_build_" + tag
9098 with self._lock:
9199 self.active_containers.add(container_name)
92100
93101 # then run the build itself
94 subprocess.check_call([
95 "docker", "run",
96 "--rm",
97 "--name", container_name,
98 "--volume=" + projdir + ":/synapse/source:ro",
99 "--volume=" + debsdir + ":/debs",
100 "-e", "TARGET_USERID=%i" % (os.getuid(), ),
101 "-e", "TARGET_GROUPID=%i" % (os.getgid(), ),
102 "-e", "DEB_BUILD_OPTIONS=%s" % ("nocheck" if skip_tests else ""),
103 "dh-venv-builder:" + tag,
104 ], stdout=stdout, stderr=subprocess.STDOUT)
102 subprocess.check_call(
103 [
104 "docker",
105 "run",
106 "--rm",
107 "--name",
108 container_name,
109 "--volume=" + projdir + ":/synapse/source:ro",
110 "--volume=" + debsdir + ":/debs",
111 "-e",
112 "TARGET_USERID=%i" % (os.getuid(),),
113 "-e",
114 "TARGET_GROUPID=%i" % (os.getgid(),),
115 "-e",
116 "DEB_BUILD_OPTIONS=%s" % ("nocheck" if skip_tests else ""),
117 "dh-venv-builder:" + tag,
118 ],
119 stdout=stdout,
120 stderr=subprocess.STDOUT,
121 )
105122
106123 with self._lock:
107124 self.active_containers.remove(container_name)
108125
109126 if stdout is not None:
110127 stdout.close()
111 print("Completed build of %s" % (dist, ))
128 print("Completed build of %s" % (dist,))
112129
113130 def kill_containers(self):
114131 with self._lock:
116133
117134 for c in active:
118135 print("killing container %s" % (c,))
119 subprocess.run([
120 "docker", "kill", c,
121 ], stdout=subprocess.DEVNULL)
136 subprocess.run(
137 [
138 "docker",
139 "kill",
140 c,
141 ],
142 stdout=subprocess.DEVNULL,
143 )
122144 with self._lock:
123145 self.active_containers.remove(c)
124146
129151 def sig(signum, _frame):
130152 print("Caught SIGINT")
131153 builder.kill_containers()
154
132155 signal.signal(signal.SIGINT, sig)
133156
134157 with ThreadPoolExecutor(max_workers=jobs) as e:
135158 res = e.map(lambda dist: builder.run_build(dist, skip_tests), dists)
136159
137160 # make sure we consume the iterable so that exceptions are raised.
138 for r in res:
161 for _ in res:
139162 pass
140163
141164
142 if __name__ == '__main__':
165 if __name__ == "__main__":
143166 parser = argparse.ArgumentParser(
144167 description=DESC,
145168 )
146169 parser.add_argument(
147 '-j', '--jobs', type=int, default=1,
148 help='specify the number of builds to run in parallel',
170 "-j",
171 "--jobs",
172 type=int,
173 default=1,
174 help="specify the number of builds to run in parallel",
149175 )
150176 parser.add_argument(
151 '--no-check', action='store_true',
152 help='skip running tests after building',
177 "--no-check",
178 action="store_true",
179 help="skip running tests after building",
153180 )
154181 parser.add_argument(
155 'dist', nargs='*', default=DISTS,
156 help='a list of distributions to build for. Default: %(default)s',
182 "dist",
183 nargs="*",
184 default=DISTS,
185 help="a list of distributions to build for. Default: %(default)s",
157186 )
158187 args = parser.parse_args()
159188 run_builds(dists=args.dist, jobs=args.jobs, skip_tests=args.no_check)
88 # run tests with that. This can be overridden to use a custom Complement
99 # checkout by setting the COMPLEMENT_DIR environment variable to the
1010 # filepath of a local Complement checkout.
11 #
12 # By default Synapse is run in monolith mode. This can be overridden by
13 # setting the WORKERS environment variable.
1114 #
1215 # A regular expression of test method names can be supplied as the first
1316 # argument to the script. Complement will then only run those tests. If
3134 echo "Checkout available at 'complement-master'"
3235 fi
3336
37 # If we're using workers, modify the docker files slightly.
38 if [[ -n "$WORKERS" ]]; then
39 BASE_IMAGE=matrixdotorg/synapse-workers
40 BASE_DOCKERFILE=docker/Dockerfile-workers
41 export COMPLEMENT_BASE_IMAGE=complement-synapse-workers
42 COMPLEMENT_DOCKERFILE=SynapseWorkers.Dockerfile
43 # And provide some more configuration to complement.
44 export COMPLEMENT_CA=true
45 export COMPLEMENT_VERSION_CHECK_ITERATIONS=500
46 else
47 BASE_IMAGE=matrixdotorg/synapse
48 BASE_DOCKERFILE=docker/Dockerfile
49 export COMPLEMENT_BASE_IMAGE=complement-synapse
50 COMPLEMENT_DOCKERFILE=Synapse.Dockerfile
51 fi
52
3453 # Build the base Synapse image from the local checkout
35 docker build -t matrixdotorg/synapse -f docker/Dockerfile .
54 docker build -t $BASE_IMAGE -f "$BASE_DOCKERFILE" .
3655 # Build the Synapse monolith image from Complement, based on the above image we just built
37 docker build -t complement-synapse -f "$COMPLEMENT_DIR/dockerfiles/Synapse.Dockerfile" "$COMPLEMENT_DIR/dockerfiles"
56 docker build -t $COMPLEMENT_BASE_IMAGE -f "$COMPLEMENT_DIR/dockerfiles/$COMPLEMENT_DOCKERFILE" "$COMPLEMENT_DIR/dockerfiles"
3857
3958 cd "$COMPLEMENT_DIR"
4059
4564 fi
4665
4766 # Run the tests!
48 COMPLEMENT_BASE_IMAGE=complement-synapse go test -v -tags synapse_blacklist,msc2946,msc3083 -count=1 $EXTRA_COMPLEMENT_ARGS ./tests
67 go test -v -tags synapse_blacklist,msc2946,msc3083 -count=1 $EXTRA_COMPLEMENT_ARGS ./tests
0 import hashlib
10 import json
21 import sys
32 import time
5352 "server_name": server_name,
5453 "verify_keys": {key_id: {"key": key} for key_id, key in keys.items()},
5554 "valid_until_ts": valid_until,
56 "tls_fingerprints": [fingerprint(certificate)],
5755 }
58
59
60 def fingerprint(certificate):
61 finger = hashlib.sha256(certificate)
62 return {"sha256": encode_base64(finger.digest())}
6356
6457
6558 def rows_v2(server, json):
7979 # then lint everything!
8080 if [[ -z ${files+x} ]]; then
8181 # Lint all source code files and directories
82 # Note: this list aims the mirror the one in tox.ini
83 files=("synapse" "docker" "tests" "scripts-dev" "scripts" "contrib" "synctl" "setup.py" "synmark" "stubs" ".buildkite")
82 # Note: this list aims to mirror the one in tox.ini
83 files=(
84 "synapse" "docker" "tests"
85 # annoyingly, black doesn't find these so we have to list them
86 "scripts/export_signing_key"
87 "scripts/generate_config"
88 "scripts/generate_log_config"
89 "scripts/hash_password"
90 "scripts/register_new_matrix_user"
91 "scripts/synapse_port_db"
92 "scripts-dev"
93 "scripts-dev/build_debian_packages"
94 "scripts-dev/sign_json"
95 "scripts-dev/update_database"
96 "contrib" "synctl" "setup.py" "synmark" "stubs" ".buildkite"
97 )
8498 fi
8599 fi
86100
4646 except ImportError:
4747 pass
4848
49 __version__ = "1.34.0"
49 __version__ = "1.35.0"
5050
5151 if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
5252 # We import here so that we don't have to install a bunch of deps when
8686 )
8787 self._track_appservice_user_ips = hs.config.track_appservice_user_ips
8888 self._macaroon_secret_key = hs.config.macaroon_secret_key
89 self._force_tracing_for_users = hs.config.tracing.force_tracing_for_users
8990
9091 async def check_from_context(
9192 self, room_version: str, event, context, do_sig_check=True
207208 opentracing.set_tag("authenticated_entity", user_id)
208209 opentracing.set_tag("user_id", user_id)
209210 opentracing.set_tag("appservice_id", app_service.id)
211 if user_id in self._force_tracing_for_users:
212 opentracing.set_tag(opentracing.tags.SAMPLING_PRIORITY, 1)
210213
211214 return requester
212215
259262 opentracing.set_tag("user_id", user_info.user_id)
260263 if device_id:
261264 opentracing.set_tag("device_id", device_id)
265 if user_info.token_owner in self._force_tracing_for_users:
266 opentracing.set_tag(opentracing.tags.SAMPLING_PRIORITY, 1)
262267
263268 return requester
264269 except KeyError:
6060 from synapse.replication.slave.storage.receipts import SlavedReceiptsStore
6161 from synapse.replication.slave.storage.registration import SlavedRegistrationStore
6262 from synapse.replication.slave.storage.room import RoomStore
63 from synapse.replication.slave.storage.transactions import SlavedTransactionStore
6463 from synapse.rest.admin import register_servlets_for_media_repo
6564 from synapse.rest.client.v1 import events, login, presence, room
6665 from synapse.rest.client.v1.initial_sync import InitialSyncRestServlet
236235 DirectoryStore,
237236 SlavedApplicationServiceStore,
238237 SlavedRegistrationStore,
239 SlavedTransactionStore,
240238 SlavedProfileStore,
241239 SlavedClientIpStore,
242240 SlavedFilteringStore,
2828 self.msc2858_enabled = experimental.get("msc2858_enabled", False) # type: bool
2929
3030 # Spaces (MSC1772, MSC2946, MSC3083, etc)
31 self.spaces_enabled = experimental.get("spaces_enabled", False) # type: bool
31 self.spaces_enabled = experimental.get("spaces_enabled", True) # type: bool
3232 if self.spaces_enabled:
3333 KNOWN_ROOM_VERSIONS[RoomVersions.MSC3083.identifier] = RoomVersions.MSC3083
3434
3535 # MSC3026 (busy presence state)
3636 self.msc3026_enabled = experimental.get("msc3026_enabled", False) # type: bool
37
38 def generate_config_section(self, **kwargs):
39 return """\
40 # Enable experimental features in Synapse.
41 #
42 # Experimental features might break or be removed without a deprecation
43 # period.
44 #
45 experimental_features:
46 # Support for Spaces (MSC1772), it enables the following:
47 #
48 # * The Spaces Summary API (MSC2946).
49 # * Restricting room membership based on space membership (MSC3083).
50 #
51 # Uncomment to disable support for Spaces.
52 #spaces_enabled: false
53 """
5656
5757 config_classes = [
5858 ServerConfig,
59 ExperimentalConfig,
6059 TlsConfig,
6160 FederationConfig,
6261 CacheConfig,
9392 TracerConfig,
9493 WorkerConfig,
9594 RedisConfig,
95 ExperimentalConfig,
9696 ]
348348
349349 def read_arguments(self, args):
350350 if args.enable_registration is not None:
351 self.enable_registration = bool(strtobool(str(args.enable_registration)))
351 self.enable_registration = strtobool(str(args.enable_registration))
163163 config_path = saml2_config.get("config_path", None)
164164 if config_path is not None:
165165 mod = load_python_module(config_path)
166 _dict_merge(merge_dict=mod.CONFIG, into_dict=saml2_config_dict)
166 config = getattr(mod, "CONFIG", None)
167 if config is None:
168 raise ConfigError(
169 "Config path specified by saml2_config.config_path does not "
170 "have a CONFIG property."
171 )
172 _dict_merge(merge_dict=config, into_dict=saml2_config_dict)
167173
168174 import saml2.config
169175
1515 import os
1616 import warnings
1717 from datetime import datetime
18 from hashlib import sha256
1918 from typing import List, Optional, Pattern
20
21 from unpaddedbase64 import encode_base64
2219
2320 from OpenSSL import SSL, crypto
2421 from twisted.internet._sslverify import Certificate, trustRootFromCertificates
8178 "tls_private_key_path must be specified if TLS-enabled listeners are "
8279 "configured."
8380 )
84
85 self._original_tls_fingerprints = config.get("tls_fingerprints", [])
86
87 if self._original_tls_fingerprints is None:
88 self._original_tls_fingerprints = []
89
90 self.tls_fingerprints = list(self._original_tls_fingerprints)
9181
9282 # Whether to verify certificates on outbound federation traffic
9383 self.federation_verify_certificates = config.get(
247237 e,
248238 )
249239
250 self.tls_fingerprints = list(self._original_tls_fingerprints)
251
252 if self.tls_certificate:
253 # Check that our own certificate is included in the list of fingerprints
254 # and include it if it is not.
255 x509_certificate_bytes = crypto.dump_certificate(
256 crypto.FILETYPE_ASN1, self.tls_certificate
257 )
258 sha256_fingerprint = encode_base64(sha256(x509_certificate_bytes).digest())
259 sha256_fingerprints = {f["sha256"] for f in self.tls_fingerprints}
260 if sha256_fingerprint not in sha256_fingerprints:
261 self.tls_fingerprints.append({"sha256": sha256_fingerprint})
262
263240 def generate_config_section(
264241 self,
265242 config_dir_path,
442419 # If unspecified, we will use CONFDIR/client.key.
443420 #
444421 account_key_file: %(default_acme_account_file)s
445
446 # List of allowed TLS fingerprints for this server to publish along
447 # with the signing keys for this server. Other matrix servers that
448 # make HTTPS requests to this server will check that the TLS
449 # certificates returned by this server match one of the fingerprints.
450 #
451 # Synapse automatically adds the fingerprint of its own certificate
452 # to the list. So if federation traffic is handled directly by synapse
453 # then no modification to the list is required.
454 #
455 # If synapse is run behind a load balancer that handles the TLS then it
456 # will be necessary to add the fingerprints of the certificates used by
457 # the loadbalancers to this list if they are different to the one
458 # synapse is using.
459 #
460 # Homeservers are permitted to cache the list of TLS fingerprints
461 # returned in the key responses up to the "valid_until_ts" returned in
462 # key. It may be necessary to publish the fingerprints of a new
463 # certificate and wait until the "valid_until_ts" of the previous key
464 # responses have passed before deploying it.
465 #
466 # You can calculate a fingerprint from a given TLS listener via:
467 # openssl s_client -connect $host:$port < /dev/null 2> /dev/null |
468 # openssl x509 -outform DER | openssl sha256 -binary | base64 | tr -d '='
469 # or by checking matrix.org/federationtester/api/report?server_name=$host
470 #
471 #tls_fingerprints: [{"sha256": "<base64_encoded_sha256_fingerprint>"}]
472422 """
473423 # Lowercase the string representation of boolean values
474424 % {
1010 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1111 # See the License for the specific language governing permissions and
1212 # limitations under the License.
13
14 from typing import Set
1315
1416 from synapse.python_dependencies import DependencyException, check_requirements
1517
3133 {"sampler": {"type": "const", "param": 1}, "logging": False},
3234 )
3335
36 self.force_tracing_for_users: Set[str] = set()
37
3438 if not self.opentracer_enabled:
3539 return
3640
4650 self.opentracer_whitelist = opentracing_config.get("homeserver_whitelist", [])
4751 if not isinstance(self.opentracer_whitelist, list):
4852 raise ConfigError("Tracer homeserver_whitelist config is malformed")
53
54 force_tracing_for_users = opentracing_config.get("force_tracing_for_users", [])
55 if not isinstance(force_tracing_for_users, list):
56 raise ConfigError(
57 "Expected a list", ("opentracing", "force_tracing_for_users")
58 )
59 for i, u in enumerate(force_tracing_for_users):
60 if not isinstance(u, str):
61 raise ConfigError(
62 "Expected a string",
63 ("opentracing", "force_tracing_for_users", f"index {i}"),
64 )
65 self.force_tracing_for_users.add(u)
4966
5067 def generate_config_section(cls, **kwargs):
5168 return """\
6380 #enabled: true
6481
6582 # The list of homeservers we wish to send and receive span contexts and span baggage.
66 # See docs/opentracing.rst
83 # See docs/opentracing.rst.
84 #
6785 # This is a list of regexes which are matched against the server_name of the
6886 # homeserver.
6987 #
7290 #homeserver_whitelist:
7391 # - ".*"
7492
93 # A list of the matrix IDs of users whose requests will always be traced,
94 # even if the tracing system would otherwise drop the traces due to
95 # probabilistic sampling.
96 #
97 # By default, the list is empty.
98 #
99 #force_tracing_for_users:
100 # - "@user1:server_name"
101 # - "@user2:server_name"
102
75103 # Jaeger can be configured to sample traces at different rates.
76104 # All configuration options provided by Jaeger can be set here.
77 # Jaeger's configuration mostly related to trace sampling which
105 # Jaeger's configuration is mostly related to trace sampling which
78106 # is documented here:
79 # https://www.jaegertracing.io/docs/1.13/sampling/.
107 # https://www.jaegertracing.io/docs/latest/sampling/.
80108 #
81109 #jaeger_config:
82110 # sampler:
83111 # type: const
84112 # param: 1
85
86 # Logging whether spans were started and reported
87 #
88113 # logging:
89114 # false
90115 """
1616 import logging
1717 import urllib
1818 from collections import defaultdict
19 from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple
19 from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Optional, Set, Tuple
2020
2121 import attr
2222 from signedjson.key import (
4141 SynapseError,
4242 )
4343 from synapse.config.key import TrustedKeyServer
44 from synapse.events import EventBase
45 from synapse.events.utils import prune_event_dict
4446 from synapse.logging.context import (
4547 PreserveLoggingContext,
4648 make_deferred_yieldable,
6870 Attributes:
6971 server_name: The name of the server to verify against.
7072
71 json_object: The JSON object to verify.
73 get_json_object: A callback to fetch the JSON object to verify.
74 A callback is used to allow deferring the creation of the JSON
75 object to verify until needed, e.g. for events we can defer
76 creating the redacted copy. This reduces the memory usage when
77 there are large numbers of in flight requests.
7278
7379 minimum_valid_until_ts: time at which we require the signing key to
7480 be valid. (0 implies we don't care)
8793 """
8894
8995 server_name = attr.ib(type=str)
90 json_object = attr.ib(type=JsonDict)
96 get_json_object = attr.ib(type=Callable[[], JsonDict])
9197 minimum_valid_until_ts = attr.ib(type=int)
9298 request_name = attr.ib(type=str)
93 key_ids = attr.ib(init=False, type=List[str])
99 key_ids = attr.ib(type=List[str])
94100 key_ready = attr.ib(default=attr.Factory(defer.Deferred), type=defer.Deferred)
95101
96 def __attrs_post_init__(self):
97 self.key_ids = signature_ids(self.json_object, self.server_name)
102 @staticmethod
103 def from_json_object(
104 server_name: str,
105 json_object: JsonDict,
106 minimum_valid_until_ms: int,
107 request_name: str,
108 ):
109 """Create a VerifyJsonRequest to verify all signatures on a signed JSON
110 object for the given server.
111 """
112 key_ids = signature_ids(json_object, server_name)
113 return VerifyJsonRequest(
114 server_name,
115 lambda: json_object,
116 minimum_valid_until_ms,
117 request_name=request_name,
118 key_ids=key_ids,
119 )
120
121 @staticmethod
122 def from_event(
123 server_name: str,
124 event: EventBase,
125 minimum_valid_until_ms: int,
126 ):
127 """Create a VerifyJsonRequest to verify all signatures on an event
128 object for the given server.
129 """
130 key_ids = list(event.signatures.get(server_name, []))
131 return VerifyJsonRequest(
132 server_name,
133 # We defer creating the redacted json object, as it uses a lot more
134 # memory than the Event object itself.
135 lambda: prune_event_dict(event.room_version, event.get_pdu_json()),
136 minimum_valid_until_ms,
137 request_name=event.event_id,
138 key_ids=key_ids,
139 )
98140
99141
100142 class KeyLookupError(ValueError):
146188 Deferred[None]: completes if the the object was correctly signed, otherwise
147189 errbacks with an error
148190 """
149 req = VerifyJsonRequest(server_name, json_object, validity_time, request_name)
150 requests = (req,)
191 request = VerifyJsonRequest.from_json_object(
192 server_name,
193 json_object,
194 validity_time,
195 request_name,
196 )
197 requests = (request,)
151198 return make_deferred_yieldable(self._verify_objects(requests)[0])
152199
153200 def verify_json_objects_for_server(
174221 logcontext.
175222 """
176223 return self._verify_objects(
177 VerifyJsonRequest(server_name, json_object, validity_time, request_name)
224 VerifyJsonRequest.from_json_object(
225 server_name, json_object, validity_time, request_name
226 )
178227 for server_name, json_object, validity_time, request_name in server_and_json
228 )
229
230 def verify_events_for_server(
231 self, server_and_events: Iterable[Tuple[str, EventBase, int]]
232 ) -> List[defer.Deferred]:
233 """Bulk verification of signatures on events.
234
235 Args:
236 server_and_events:
237 Iterable of `(server_name, event, validity_time)` tuples.
238
239 `server_name` is which server we are verifying the signature for
240 on the event.
241
242 `event` is the event that we'll verify the signatures of for
243 the given `server_name`.
244
245 `validity_time` is a timestamp at which the signing key must be
246 valid.
247
248 Returns:
249 List<Deferred[None]>: for each input triplet, a deferred indicating success
250 or failure to verify each event's signature for the given
251 server_name. The deferreds run their callbacks in the sentinel
252 logcontext.
253 """
254 return self._verify_objects(
255 VerifyJsonRequest.from_event(server_name, event, validity_time)
256 for server_name, event, validity_time in server_and_events
179257 )
180258
181259 def _verify_objects(
891969 with PreserveLoggingContext():
892970 _, key_id, verify_key = await verify_request.key_ready
893971
894 json_object = verify_request.json_object
972 json_object = verify_request.get_json_object()
895973
896974 try:
897975 verify_signed_json(json_object, server_name, verify_key)
136136 return deferreds
137137
138138
139 class PduToCheckSig(
140 namedtuple(
141 "PduToCheckSig", ["pdu", "redacted_pdu_json", "sender_domain", "deferreds"]
142 )
143 ):
139 class PduToCheckSig(namedtuple("PduToCheckSig", ["pdu", "sender_domain", "deferreds"])):
144140 pass
145141
146142
183179 pdus_to_check = [
184180 PduToCheckSig(
185181 pdu=p,
186 redacted_pdu_json=prune_event(p).get_pdu_json(),
187182 sender_domain=get_domain_from_id(p.sender),
188183 deferreds=[],
189184 )
194189 # (except if its a 3pid invite, in which case it may be sent by any server)
195190 pdus_to_check_sender = [p for p in pdus_to_check if not _is_invite_via_3pid(p.pdu)]
196191
197 more_deferreds = keyring.verify_json_objects_for_server(
192 more_deferreds = keyring.verify_events_for_server(
198193 [
199194 (
200195 p.sender_domain,
201 p.redacted_pdu_json,
196 p.pdu,
202197 p.pdu.origin_server_ts if room_version.enforce_key_validity else 0,
203 p.pdu.event_id,
204198 )
205199 for p in pdus_to_check_sender
206200 ]
229223 if p.sender_domain != get_domain_from_id(p.pdu.event_id)
230224 ]
231225
232 more_deferreds = keyring.verify_json_objects_for_server(
226 more_deferreds = keyring.verify_events_for_server(
233227 [
234228 (
235229 get_domain_from_id(p.pdu.event_id),
236 p.redacted_pdu_json,
230 p.pdu,
237231 p.pdu.origin_server_ts if room_version.enforce_key_validity else 0,
238 p.pdu.event_id,
239232 )
240233 for p in pdus_to_check_event_id
241234 ]
5454 )
5555 from synapse.events import EventBase, builder
5656 from synapse.federation.federation_base import FederationBase, event_from_pdu_json
57 from synapse.federation.transport.client import SendJoinResponse
5758 from synapse.logging.context import make_deferred_yieldable, preserve_fn
5859 from synapse.logging.utils import log_function
5960 from synapse.types import JsonDict, get_domain_from_id
664665 """
665666
666667 async def send_request(destination) -> Dict[str, Any]:
667 content = await self._do_send_join(destination, pdu)
668
669 logger.debug("Got content: %s", content)
670
671 state = [
672 event_from_pdu_json(p, room_version, outlier=True)
673 for p in content.get("state", [])
674 ]
675
676 auth_chain = [
677 event_from_pdu_json(p, room_version, outlier=True)
678 for p in content.get("auth_chain", [])
679 ]
668 response = await self._do_send_join(room_version, destination, pdu)
669
670 state = response.state
671 auth_chain = response.auth_events
680672
681673 pdus = {p.event_id: p for p in itertools.chain(state, auth_chain)}
682674
751743
752744 return await self._try_destination_list("send_join", destinations, send_request)
753745
754 async def _do_send_join(self, destination: str, pdu: EventBase) -> JsonDict:
746 async def _do_send_join(
747 self, room_version: RoomVersion, destination: str, pdu: EventBase
748 ) -> SendJoinResponse:
755749 time_now = self._clock.time_msec()
756750
757751 try:
758752 return await self.transport_layer.send_join_v2(
753 room_version=room_version,
759754 destination=destination,
760755 room_id=pdu.room_id,
761756 event_id=pdu.event_id,
770765
771766 logger.debug("Couldn't send_join with the v2 API, falling back to the v1 API")
772767
773 resp = await self.transport_layer.send_join_v1(
768 return await self.transport_layer.send_join_v1(
769 room_version=room_version,
774770 destination=destination,
775771 room_id=pdu.room_id,
776772 event_id=pdu.event_id,
777773 content=pdu.get_pdu_json(time_now),
778774 )
779
780 # We expect the v1 API to respond with [200, content], so we only return the
781 # content.
782 return resp[1]
783775
784776 async def send_invite(
785777 self,
1616 import urllib
1717 from typing import Any, Dict, List, Optional
1818
19 import attr
20 import ijson
21
1922 from synapse.api.constants import Membership
2023 from synapse.api.errors import Codes, HttpResponseException, SynapseError
24 from synapse.api.room_versions import RoomVersion
2125 from synapse.api.urls import (
2226 FEDERATION_UNSTABLE_PREFIX,
2327 FEDERATION_V1_PREFIX,
2428 FEDERATION_V2_PREFIX,
2529 )
30 from synapse.events import EventBase, make_event_from_dict
31 from synapse.http.matrixfederationclient import ByteParser
2632 from synapse.logging.utils import log_function
2733 from synapse.types import JsonDict
2834
2935 logger = logging.getLogger(__name__)
36
37 # Send join responses can be huge, so we set a separate limit here. The response
38 # is parsed in a streaming manner, which helps alleviate the issue of memory
39 # usage a bit.
40 MAX_RESPONSE_SIZE_SEND_JOIN = 500 * 1024 * 1024
3041
3142
3243 class TransportLayerClient:
239250 return content
240251
241252 @log_function
242 async def send_join_v1(self, destination, room_id, event_id, content):
253 async def send_join_v1(
254 self,
255 room_version,
256 destination,
257 room_id,
258 event_id,
259 content,
260 ) -> "SendJoinResponse":
243261 path = _create_v1_path("/send_join/%s/%s", room_id, event_id)
244262
245263 response = await self.client.put_json(
246 destination=destination, path=path, data=content
264 destination=destination,
265 path=path,
266 data=content,
267 parser=SendJoinParser(room_version, v1_api=True),
268 max_response_size=MAX_RESPONSE_SIZE_SEND_JOIN,
247269 )
248270
249271 return response
250272
251273 @log_function
252 async def send_join_v2(self, destination, room_id, event_id, content):
274 async def send_join_v2(
275 self, room_version, destination, room_id, event_id, content
276 ) -> "SendJoinResponse":
253277 path = _create_v2_path("/send_join/%s/%s", room_id, event_id)
254278
255279 response = await self.client.put_json(
256 destination=destination, path=path, data=content
280 destination=destination,
281 path=path,
282 data=content,
283 parser=SendJoinParser(room_version, v1_api=False),
284 max_response_size=MAX_RESPONSE_SIZE_SEND_JOIN,
257285 )
258286
259287 return response
10521080 str
10531081 """
10541082 return _create_path(FEDERATION_V2_PREFIX, path, *args)
1083
1084
1085 @attr.s(slots=True, auto_attribs=True)
1086 class SendJoinResponse:
1087 """The parsed response of a `/send_join` request."""
1088
1089 auth_events: List[EventBase]
1090 state: List[EventBase]
1091
1092
1093 @ijson.coroutine
1094 def _event_list_parser(room_version: RoomVersion, events: List[EventBase]):
1095 """Helper function for use with `ijson.items_coro` to parse an array of
1096 events and add them to the given list.
1097 """
1098
1099 while True:
1100 obj = yield
1101 event = make_event_from_dict(obj, room_version)
1102 events.append(event)
1103
1104
1105 class SendJoinParser(ByteParser[SendJoinResponse]):
1106 """A parser for the response to `/send_join` requests.
1107
1108 Args:
1109 room_version: The version of the room.
1110 v1_api: Whether the response is in the v1 format.
1111 """
1112
1113 CONTENT_TYPE = "application/json"
1114
1115 def __init__(self, room_version: RoomVersion, v1_api: bool):
1116 self._response = SendJoinResponse([], [])
1117
1118 # The V1 API has the shape of `[200, {...}]`, which we handle by
1119 # prefixing with `item.*`.
1120 prefix = "item." if v1_api else ""
1121
1122 self._coro_state = ijson.items_coro(
1123 _event_list_parser(room_version, self._response.state),
1124 prefix + "state.item",
1125 )
1126 self._coro_auth = ijson.items_coro(
1127 _event_list_parser(room_version, self._response.auth_events),
1128 prefix + "auth_chain.item",
1129 )
1130
1131 def write(self, data: bytes) -> int:
1132 self._coro_state.send(data)
1133 self._coro_auth.send(data)
1134
1135 return len(data)
1136
1137 def finish(self) -> SendJoinResponse:
1138 return self._response
159159 # If we get a valid signed request from the other side, its probably
160160 # alive
161161 retry_timings = await self.store.get_destination_retry_timings(origin)
162 if retry_timings and retry_timings["retry_last_ts"]:
162 if retry_timings and retry_timings.retry_last_ts:
163163 run_in_background(self._reset_retry_timings, origin)
164164
165165 return origin
13971397 )
13981398
13991399 return 200, await self.handler.federation_space_summary(
1400 room_id, suggested_only, max_rooms_per_space, exclude_rooms
1400 origin, room_id, suggested_only, max_rooms_per_space, exclude_rooms
14011401 )
14021402
14031403 # TODO When switching to the stable endpoint, remove the POST handler.
14271427 )
14281428
14291429 return 200, await self.handler.federation_space_summary(
1430 room_id, suggested_only, max_rooms_per_space, exclude_rooms
1430 origin, room_id, suggested_only, max_rooms_per_space, exclude_rooms
14311431 )
14321432
14331433
1414 import email.mime.multipart
1515 import email.utils
1616 import logging
17 from email.mime.multipart import MIMEMultipart
18 from email.mime.text import MIMEText
1917 from typing import TYPE_CHECKING, List, Optional, Tuple
2018
2119 from synapse.api.errors import StoreError, SynapseError
22 from synapse.logging.context import make_deferred_yieldable
2320 from synapse.metrics.background_process_metrics import wrap_as_background_process
2421 from synapse.types import UserID
2522 from synapse.util import stringutils
3532 self.hs = hs
3633 self.config = hs.config
3734 self.store = self.hs.get_datastore()
38 self.sendmail = self.hs.get_sendmail()
35 self.send_email_handler = self.hs.get_send_email_handler()
3936 self.clock = self.hs.get_clock()
37
38 self._app_name = self.hs.config.email_app_name
4039
4140 self._account_validity_enabled = (
4241 hs.config.account_validity.account_validity_enabled
6261 self._template_text = (
6362 hs.config.account_validity.account_validity_template_text
6463 )
65 account_validity_renew_email_subject = (
64 self._renew_email_subject = (
6665 hs.config.account_validity.account_validity_renew_email_subject
6766 )
68
69 try:
70 app_name = hs.config.email_app_name
71
72 self._subject = account_validity_renew_email_subject % {"app": app_name}
73
74 self._from_string = hs.config.email_notif_from % {"app": app_name}
75 except Exception:
76 # If substitution failed, fall back to the bare strings.
77 self._subject = account_validity_renew_email_subject
78 self._from_string = hs.config.email_notif_from
79
80 self._raw_from = email.utils.parseaddr(self._from_string)[1]
8167
8268 # Check the renewal emails to send and send them every 30min.
8369 if hs.config.run_background_tasks:
158144 }
159145
160146 html_text = self._template_html.render(**template_vars)
161 html_part = MIMEText(html_text, "html", "utf8")
162
163147 plain_text = self._template_text.render(**template_vars)
164 text_part = MIMEText(plain_text, "plain", "utf8")
165148
166149 for address in addresses:
167150 raw_to = email.utils.parseaddr(address)[1]
168151
169 multipart_msg = MIMEMultipart("alternative")
170 multipart_msg["Subject"] = self._subject
171 multipart_msg["From"] = self._from_string
172 multipart_msg["To"] = address
173 multipart_msg["Date"] = email.utils.formatdate()
174 multipart_msg["Message-ID"] = email.utils.make_msgid()
175 multipart_msg.attach(text_part)
176 multipart_msg.attach(html_part)
177
178 logger.info("Sending renewal email to %s", address)
179
180 await make_deferred_yieldable(
181 self.sendmail(
182 self.hs.config.email_smtp_host,
183 self._raw_from,
184 raw_to,
185 multipart_msg.as_string().encode("utf8"),
186 reactor=self.hs.get_reactor(),
187 port=self.hs.config.email_smtp_port,
188 requireAuthentication=self.hs.config.email_smtp_user is not None,
189 username=self.hs.config.email_smtp_user,
190 password=self.hs.config.email_smtp_pass,
191 requireTransportSecurity=self.hs.config.require_transport_security,
192 )
152 await self.send_email_handler.send_email(
153 email_address=raw_to,
154 subject=self._renew_email_subject,
155 app_name=self._app_name,
156 html=html_text,
157 text=plain_text,
193158 )
194159
195160 await self.store.set_renewal_mail_status(user_id=user_id, email_sent=True)
1010 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1111 # See the License for the specific language governing permissions and
1212 # limitations under the License.
13 from typing import TYPE_CHECKING
13 from typing import TYPE_CHECKING, Collection, Optional
1414
15 from synapse.api.constants import EventTypes, JoinRules
15 from synapse.api.constants import EventTypes, JoinRules, Membership
16 from synapse.api.errors import AuthError
1617 from synapse.api.room_versions import RoomVersion
18 from synapse.events import EventBase
1719 from synapse.types import StateMap
1820
1921 if TYPE_CHECKING:
2830 def __init__(self, hs: "HomeServer"):
2931 self._store = hs.get_datastore()
3032
31 async def can_join_without_invite(
32 self, state_ids: StateMap[str], room_version: RoomVersion, user_id: str
33 ) -> bool:
33 async def check_restricted_join_rules(
34 self,
35 state_ids: StateMap[str],
36 room_version: RoomVersion,
37 user_id: str,
38 prev_member_event: Optional[EventBase],
39 ) -> None:
3440 """
35 Check whether a user can join a room without an invite.
41 Check whether a user can join a room without an invite due to restricted join rules.
3642
3743 When joining a room with restricted joined rules (as defined in MSC3083),
38 the membership of spaces must be checked during join.
44 the membership of spaces must be checked during a room join.
3945
4046 Args:
4147 state_ids: The state of the room as it currently is.
4248 room_version: The room version of the room being joined.
4349 user_id: The user joining the room.
50 prev_member_event: The current membership event for this user.
51
52 Raises:
53 AuthError if the user cannot join the room.
54 """
55 # If the member is invited or currently joined, then nothing to do.
56 if prev_member_event and (
57 prev_member_event.membership in (Membership.JOIN, Membership.INVITE)
58 ):
59 return
60
61 # This is not a room with a restricted join rule, so we don't need to do the
62 # restricted room specific checks.
63 #
64 # Note: We'll be applying the standard join rule checks later, which will
65 # catch the cases of e.g. trying to join private rooms without an invite.
66 if not await self.has_restricted_join_rules(state_ids, room_version):
67 return
68
69 # Get the spaces which allow access to this room and check if the user is
70 # in any of them.
71 allowed_spaces = await self.get_spaces_that_allow_join(state_ids)
72 if not await self.is_user_in_rooms(allowed_spaces, user_id):
73 raise AuthError(
74 403,
75 "You do not belong to any of the required spaces to join this room.",
76 )
77
78 async def has_restricted_join_rules(
79 self, state_ids: StateMap[str], room_version: RoomVersion
80 ) -> bool:
81 """
82 Return if the room has the proper join rules set for access via spaces.
83
84 Args:
85 state_ids: The state of the room as it currently is.
86 room_version: The room version of the room to query.
4487
4588 Returns:
46 True if the user can join the room, false otherwise.
89 True if the proper room version and join rules are set for restricted access.
4790 """
4891 # This only applies to room versions which support the new join rule.
4992 if not room_version.msc3083_join_rules:
50 return True
93 return False
5194
5295 # If there's no join rule, then it defaults to invite (so this doesn't apply).
5396 join_rules_event_id = state_ids.get((EventTypes.JoinRules, ""), None)
5497 if not join_rules_event_id:
55 return True
98 return False
5699
57100 # If the join rule is not restricted, this doesn't apply.
58101 join_rules_event = await self._store.get_event(join_rules_event_id)
59 if join_rules_event.content.get("join_rule") != JoinRules.MSC3083_RESTRICTED:
60 return True
102 return join_rules_event.content.get("join_rule") == JoinRules.MSC3083_RESTRICTED
103
104 async def get_spaces_that_allow_join(
105 self, state_ids: StateMap[str]
106 ) -> Collection[str]:
107 """
108 Generate a list of spaces which allow access to a room.
109
110 Args:
111 state_ids: The state of the room as it currently is.
112
113 Returns:
114 A collection of spaces which provide membership to the room.
115 """
116 # If there's no join rule, then it defaults to invite (so this doesn't apply).
117 join_rules_event_id = state_ids.get((EventTypes.JoinRules, ""), None)
118 if not join_rules_event_id:
119 return ()
120
121 # If the join rule is not restricted, this doesn't apply.
122 join_rules_event = await self._store.get_event(join_rules_event_id)
61123
62124 # If allowed is of the wrong form, then only allow invited users.
63125 allowed_spaces = join_rules_event.content.get("allow", [])
64126 if not isinstance(allowed_spaces, list):
65 return False
66
67 # Get the list of joined rooms and see if there's an overlap.
68 joined_rooms = await self._store.get_rooms_for_user(user_id)
127 return ()
69128
70129 # Pull out the other room IDs, invalid data gets filtered.
130 result = []
71131 for space in allowed_spaces:
72132 if not isinstance(space, dict):
73133 continue
76136 if not isinstance(space_id, str):
77137 continue
78138
79 # The user was joined to one of the spaces specified, they can join
80 # this room!
81 if space_id in joined_rooms:
139 result.append(space_id)
140
141 return result
142
143 async def is_user_in_rooms(self, room_ids: Collection[str], user_id: str) -> bool:
144 """
145 Check whether a user is a member of any of the provided rooms.
146
147 Args:
148 room_ids: The rooms to check for membership.
149 user_id: The user to check.
150
151 Returns:
152 True if the user is in any of the rooms, false otherwise.
153 """
154 if not room_ids:
155 return False
156
157 # Get the list of joined rooms and see if there's an overlap.
158 joined_rooms = await self._store.get_rooms_for_user(user_id)
159
160 # Check each room and see if the user is in it.
161 for room_id in room_ids:
162 if room_id in joined_rooms:
82163 return True
83164
84 # The user was not in any of the required spaces.
165 # The user was not in any of the rooms.
85166 return False
9090 get_domain_from_id,
9191 )
9292 from synapse.util.async_helpers import Linearizer, concurrently_execute
93 from synapse.util.iterutils import batch_iter
9394 from synapse.util.retryutils import NotRetryingDestination
9495 from synapse.util.stringutils import shortstr
9596 from synapse.visibility import filter_events_for_server
16671668 # Check if the user is already in the room or invited to the room.
16681669 user_id = event.state_key
16691670 prev_member_event_id = prev_state_ids.get((EventTypes.Member, user_id), None)
1670 newly_joined = True
1671 user_is_invited = False
1671 prev_member_event = None
16721672 if prev_member_event_id:
16731673 prev_member_event = await self.store.get_event(prev_member_event_id)
1674 newly_joined = prev_member_event.membership != Membership.JOIN
1675 user_is_invited = prev_member_event.membership == Membership.INVITE
1676
1677 # If the member is not already in the room, and not invited, check if
1678 # they should be allowed access via membership in a space.
1679 if (
1680 newly_joined
1681 and not user_is_invited
1682 and not await self._event_auth_handler.can_join_without_invite(
1683 prev_state_ids,
1684 event.room_version,
1685 user_id,
1686 )
1687 ):
1688 raise AuthError(
1689 403,
1690 "You do not belong to any of the required spaces to join this room.",
1691 )
1674
1675 # Check if the member should be allowed access via membership in a space.
1676 await self._event_auth_handler.check_restricted_join_rules(
1677 prev_state_ids,
1678 event.room_version,
1679 user_id,
1680 prev_member_event,
1681 )
16921682
16931683 # Persist the event.
16941684 await self._auth_and_persist_event(origin, event, context)
30633053 """
30643054 instance = self.config.worker.events_shard_config.get_instance(room_id)
30653055 if instance != self._instance_name:
3066 result = await self._send_events(
3067 instance_name=instance,
3068 store=self.store,
3069 room_id=room_id,
3070 event_and_contexts=event_and_contexts,
3071 backfilled=backfilled,
3072 )
3056 # Limit the number of events sent over federation.
3057 for batch in batch_iter(event_and_contexts, 1000):
3058 result = await self._send_events(
3059 instance_name=instance,
3060 store=self.store,
3061 room_id=room_id,
3062 event_and_contexts=batch,
3063 backfilled=backfilled,
3064 )
30733065 return result["max_stream_id"]
30743066 else:
30753067 assert self.storage.persistence
221221
222222 @abc.abstractmethod
223223 async def set_state(
224 self, target_user: UserID, state: JsonDict, ignore_status_msg: bool = False
224 self,
225 target_user: UserID,
226 state: JsonDict,
227 ignore_status_msg: bool = False,
228 force_notify: bool = False,
225229 ) -> None:
226 """Set the presence state of the user. """
230 """Set the presence state of the user.
231
232 Args:
233 target_user: The ID of the user to set the presence state of.
234 state: The presence state as a JSON dictionary.
235 ignore_status_msg: True to ignore the "status_msg" field of the `state` dict.
236 If False, the user's current status will be updated.
237 force_notify: Whether to force notification of the update to clients.
238 """
227239
228240 @abc.abstractmethod
229241 async def bump_presence_active_time(self, user: UserID):
294306
295307 for destinations, states in hosts_and_states:
296308 self._federation.send_presence_to_destinations(states, destinations)
309
310 async def send_full_presence_to_users(self, user_ids: Collection[str]):
311 """
312 Adds to the list of users who should receive a full snapshot of presence
313 upon their next sync. Note that this only works for local users.
314
315 Then, grabs the current presence state for a given set of users and adds it
316 to the top of the presence stream.
317
318 Args:
319 user_ids: The IDs of the local users to send full presence to.
320 """
321 # Retrieve one of the users from the given set
322 if not user_ids:
323 raise Exception(
324 "send_full_presence_to_users must be called with at least one user"
325 )
326 user_id = next(iter(user_ids))
327
328 # Mark all users as receiving full presence on their next sync
329 await self.store.add_users_to_send_full_presence_to(user_ids)
330
331 # Add a new entry to the presence stream. Since we use stream tokens to determine whether a
332 # local user should receive a full snapshot of presence when they sync, we need to bump the
333 # presence stream so that subsequent syncs with no presence activity in between won't result
334 # in the client receiving multiple full snapshots of presence.
335 #
336 # If we bump the stream ID, then the user will get a higher stream token next sync, and thus
337 # correctly won't receive a second snapshot.
338
339 # Get the current presence state for one of the users (defaults to offline if not found)
340 current_presence_state = await self.get_state(UserID.from_string(user_id))
341
342 # Convert the UserPresenceState object into a serializable dict
343 state = {
344 "presence": current_presence_state.state,
345 "status_message": current_presence_state.status_msg,
346 }
347
348 # Copy the presence state to the tip of the presence stream.
349
350 # We set force_notify=True here so that this presence update is guaranteed to
351 # increment the presence stream ID (which resending the current user's presence
352 # otherwise would not do).
353 await self.set_state(UserID.from_string(user_id), state, force_notify=True)
297354
298355
299356 class _NullContextManager(ContextManager[None]):
479536 target_user: UserID,
480537 state: JsonDict,
481538 ignore_status_msg: bool = False,
539 force_notify: bool = False,
482540 ) -> None:
483 """Set the presence state of the user."""
541 """Set the presence state of the user.
542
543 Args:
544 target_user: The ID of the user to set the presence state of.
545 state: The presence state as a JSON dictionary.
546 ignore_status_msg: True to ignore the "status_msg" field of the `state` dict.
547 If False, the user's current status will be updated.
548 force_notify: Whether to force notification of the update to clients.
549 """
484550 presence = state["presence"]
485551
486552 valid_presence = (
507573 user_id=user_id,
508574 state=state,
509575 ignore_status_msg=ignore_status_msg,
576 force_notify=force_notify,
510577 )
511578
512579 async def bump_presence_active_time(self, user: UserID) -> None:
676743 [self.user_to_current_state[user_id] for user_id in unpersisted]
677744 )
678745
679 async def _update_states(self, new_states: Iterable[UserPresenceState]) -> None:
746 async def _update_states(
747 self, new_states: Iterable[UserPresenceState], force_notify: bool = False
748 ) -> None:
680749 """Updates presence of users. Sets the appropriate timeouts. Pokes
681750 the notifier and federation if and only if the changed presence state
682751 should be sent to clients/servers.
683752
684753 Args:
685754 new_states: The new user presence state updates to process.
755 force_notify: Whether to force notifying clients of this presence state update,
756 even if it doesn't change the state of a user's presence (e.g online -> online).
757 This is currently used to bump the max presence stream ID without changing any
758 user's presence (see PresenceHandler.add_users_to_send_full_presence_to).
686759 """
687760 now = self.clock.time_msec()
688761
718791 wheel_timer=self.wheel_timer,
719792 now=now,
720793 )
794
795 if force_notify:
796 should_notify = True
721797
722798 self.user_to_current_state[user_id] = new_state
723799
10571133 await self._update_states(updates)
10581134
10591135 async def set_state(
1060 self, target_user: UserID, state: JsonDict, ignore_status_msg: bool = False
1136 self,
1137 target_user: UserID,
1138 state: JsonDict,
1139 ignore_status_msg: bool = False,
1140 force_notify: bool = False,
10611141 ) -> None:
1062 """Set the presence state of the user."""
1142 """Set the presence state of the user.
1143
1144 Args:
1145 target_user: The ID of the user to set the presence state of.
1146 state: The presence state as a JSON dictionary.
1147 ignore_status_msg: True to ignore the "status_msg" field of the `state` dict.
1148 If False, the user's current status will be updated.
1149 force_notify: Whether to force notification of the update to clients.
1150 """
10631151 status_msg = state.get("status_msg", None)
10641152 presence = state["presence"]
10651153
10901178 ):
10911179 new_fields["last_active_ts"] = self.clock.time_msec()
10921180
1093 await self._update_states([prev_state.copy_and_replace(**new_fields)])
1181 await self._update_states(
1182 [prev_state.copy_and_replace(**new_fields)], force_notify=force_notify
1183 )
10941184
10951185 async def is_visible(self, observed_user: UserID, observer_user: UserID) -> bool:
10961186 """Returns whether a user can see another user's presence."""
13881478 #
13891479 # Presence -> Notifier -> PresenceEventSource -> Presence
13901480 #
1391 # Same with get_module_api, get_presence_router
1481 # Same with get_presence_router:
13921482 #
13931483 # AuthHandler -> Notifier -> PresenceEventSource -> ModuleApi -> AuthHandler
13941484 self.get_presence_handler = hs.get_presence_handler
1395 self.get_module_api = hs.get_module_api
13961485 self.get_presence_router = hs.get_presence_router
13971486 self.clock = hs.get_clock()
13981487 self.store = hs.get_datastore()
14231512 stream_change_cache = self.store.presence_stream_cache
14241513
14251514 with Measure(self.clock, "presence.get_new_events"):
1426 if user_id in self.get_module_api()._send_full_presence_to_local_users:
1427 # This user has been specified by a module to receive all current, online
1428 # user presence. Removing from_key and setting include_offline to false
1429 # will do effectively this.
1430 from_key = None
1431 include_offline = False
1432
14331515 if from_key is not None:
14341516 from_key = int(from_key)
1517
1518 # Check if this user should receive all current, online user presence. We only
1519 # bother to do this if from_key is set, as otherwise the user will receive all
1520 # user presence anyways.
1521 if await self.store.should_user_receive_full_presence_with_token(
1522 user_id, from_key
1523 ):
1524 # This user has been specified by a module to receive all current, online
1525 # user presence. Removing from_key and setting include_offline to false
1526 # will do effectively this.
1527 from_key = None
1528 include_offline = False
14351529
14361530 max_token = self.store.get_current_presence_token()
14371531 if from_key == max_token:
14661560 user_id, include_offline, from_key
14671561 )
14681562
1469 # Remove the user from the list of users to receive all presence
1470 if user_id in self.get_module_api()._send_full_presence_to_local_users:
1471 self.get_module_api()._send_full_presence_to_local_users.remove(
1472 user_id
1473 )
1474
14751563 return presence_updates, max_token
14761564
14771565 # Make mypy happy. users_interested_in should now be a set
15201608 interested_and_updated_users
15211609 )
15221610 presence_updates = list(users_to_state.values())
1523
1524 # Remove the user from the list of users to receive all presence
1525 if user_id in self.get_module_api()._send_full_presence_to_local_users:
1526 self.get_module_api()._send_full_presence_to_local_users.remove(user_id)
15271611
15281612 if not include_offline:
15291613 # Filter out offline presence states
259259
260260 if event.membership == Membership.JOIN:
261261 newly_joined = True
262 user_is_invited = False
262 prev_member_event = None
263263 if prev_member_event_id:
264264 prev_member_event = await self.store.get_event(prev_member_event_id)
265265 newly_joined = prev_member_event.membership != Membership.JOIN
266 user_is_invited = prev_member_event.membership == Membership.INVITE
267
268 # If the member is not already in the room and is not accepting an invite,
269 # check if they should be allowed access via membership in a space.
270 if (
271 newly_joined
272 and not user_is_invited
273 and not await self.event_auth_handler.can_join_without_invite(
274 prev_state_ids, event.room_version, user_id
275 )
276 ):
277 raise AuthError(
278 403,
279 "You do not belong to any of the required spaces to join this room.",
280 )
266
267 # Check if the member should be allowed access via membership in a space.
268 await self.event_auth_handler.check_restricted_join_rules(
269 prev_state_ids, event.room_version, user_id, prev_member_event
270 )
281271
282272 # Only rate-limit if the user actually joined the room, otherwise we'll end
283273 # up blocking profile updates.
0 # Copyright 2021 The Matrix.org C.I.C. Foundation
1 #
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 import email.utils
15 import logging
16 from email.mime.multipart import MIMEMultipart
17 from email.mime.text import MIMEText
18 from typing import TYPE_CHECKING
19
20 from synapse.logging.context import make_deferred_yieldable
21
22 if TYPE_CHECKING:
23 from synapse.server import HomeServer
24
25 logger = logging.getLogger(__name__)
26
27
28 class SendEmailHandler:
29 def __init__(self, hs: "HomeServer"):
30 self.hs = hs
31
32 self._sendmail = hs.get_sendmail()
33 self._reactor = hs.get_reactor()
34
35 self._from = hs.config.email.email_notif_from
36 self._smtp_host = hs.config.email.email_smtp_host
37 self._smtp_port = hs.config.email.email_smtp_port
38 self._smtp_user = hs.config.email.email_smtp_user
39 self._smtp_pass = hs.config.email.email_smtp_pass
40 self._require_transport_security = hs.config.email.require_transport_security
41
42 async def send_email(
43 self,
44 email_address: str,
45 subject: str,
46 app_name: str,
47 html: str,
48 text: str,
49 ) -> None:
50 """Send a multipart email with the given information.
51
52 Args:
53 email_address: The address to send the email to.
54 subject: The email's subject.
55 app_name: The app name to include in the From header.
56 html: The HTML content to include in the email.
57 text: The plain text content to include in the email.
58 """
59 try:
60 from_string = self._from % {"app": app_name}
61 except (KeyError, TypeError):
62 from_string = self._from
63
64 raw_from = email.utils.parseaddr(from_string)[1]
65 raw_to = email.utils.parseaddr(email_address)[1]
66
67 if raw_to == "":
68 raise RuntimeError("Invalid 'to' address")
69
70 html_part = MIMEText(html, "html", "utf8")
71 text_part = MIMEText(text, "plain", "utf8")
72
73 multipart_msg = MIMEMultipart("alternative")
74 multipart_msg["Subject"] = subject
75 multipart_msg["From"] = from_string
76 multipart_msg["To"] = email_address
77 multipart_msg["Date"] = email.utils.formatdate()
78 multipart_msg["Message-ID"] = email.utils.make_msgid()
79 multipart_msg.attach(text_part)
80 multipart_msg.attach(html_part)
81
82 logger.info("Sending email to %s" % email_address)
83
84 await make_deferred_yieldable(
85 self._sendmail(
86 self._smtp_host,
87 raw_from,
88 raw_to,
89 multipart_msg.as_string().encode("utf8"),
90 reactor=self._reactor,
91 port=self._smtp_port,
92 requireAuthentication=self._smtp_user is not None,
93 username=self._smtp_user,
94 password=self._smtp_pass,
95 requireTransportSecurity=self._require_transport_security,
96 )
97 )
1515 import logging
1616 import re
1717 from collections import deque
18 from typing import TYPE_CHECKING, Iterable, List, Optional, Sequence, Set, Tuple, cast
18 from typing import TYPE_CHECKING, Iterable, List, Optional, Sequence, Set, Tuple
1919
2020 import attr
2121
22 from synapse.api.constants import EventContentFields, EventTypes, HistoryVisibility
22 from synapse.api.constants import (
23 EventContentFields,
24 EventTypes,
25 HistoryVisibility,
26 Membership,
27 )
2328 from synapse.api.errors import AuthError
2429 from synapse.events import EventBase
2530 from synapse.events.utils import format_event_for_client_v2
3136 logger = logging.getLogger(__name__)
3237
3338 # number of rooms to return. We'll stop once we hit this limit.
34 # TODO: allow clients to reduce this with a request param.
3539 MAX_ROOMS = 50
3640
3741 # max number of events to return per room.
4549 def __init__(self, hs: "HomeServer"):
4650 self._clock = hs.get_clock()
4751 self._auth = hs.get_auth()
48 self._room_list_handler = hs.get_room_list_handler()
49 self._state_handler = hs.get_state_handler()
52 self._event_auth_handler = hs.get_event_auth_handler()
5053 self._store = hs.get_datastore()
5154 self._event_serializer = hs.get_event_client_serializer()
5255 self._server_name = hs.hostname
111114 max_children = max_rooms_per_space if processed_rooms else None
112115
113116 if is_in_room:
114 rooms, events = await self._summarize_local_room(
115 requester, room_id, suggested_only, max_children
116 )
117 room, events = await self._summarize_local_room(
118 requester, None, room_id, suggested_only, max_children
119 )
120
121 logger.debug(
122 "Query of local room %s returned events %s",
123 room_id,
124 ["%s->%s" % (ev["room_id"], ev["state_key"]) for ev in events],
125 )
126
127 if room:
128 rooms_result.append(room)
117129 else:
118 rooms, events = await self._summarize_remote_room(
130 fed_rooms, fed_events = await self._summarize_remote_room(
119131 queue_entry,
120132 suggested_only,
121133 max_children,
122134 exclude_rooms=processed_rooms,
123135 )
124136
125 logger.debug(
126 "Query of %s returned rooms %s, events %s",
127 queue_entry.room_id,
128 [room.get("room_id") for room in rooms],
129 ["%s->%s" % (ev["room_id"], ev["state_key"]) for ev in events],
130 )
131
132 rooms_result.extend(rooms)
133
134 # any rooms returned don't need visiting again
135 processed_rooms.update(cast(str, room.get("room_id")) for room in rooms)
137 # The results over federation might include rooms that the we,
138 # as the requesting server, are allowed to see, but the requesting
139 # user is not permitted see.
140 #
141 # Filter the returned results to only what is accessible to the user.
142 room_ids = set()
143 events = []
144 for room in fed_rooms:
145 fed_room_id = room.get("room_id")
146 if not fed_room_id or not isinstance(fed_room_id, str):
147 continue
148
149 # The room should only be included in the summary if:
150 # a. the user is in the room;
151 # b. the room is world readable; or
152 # c. the user is in a space that has been granted access to
153 # the room.
154 #
155 # Note that we know the user is not in the root room (which is
156 # why the remote call was made in the first place), but the user
157 # could be in one of the children rooms and we just didn't know
158 # about the link.
159 include_room = room.get("world_readable") is True
160
161 # Check if the user is a member of any of the allowed spaces
162 # from the response.
163 allowed_spaces = room.get("allowed_spaces")
164 if (
165 not include_room
166 and allowed_spaces
167 and isinstance(allowed_spaces, list)
168 ):
169 include_room = await self._event_auth_handler.is_user_in_rooms(
170 allowed_spaces, requester
171 )
172
173 # Finally, if this isn't the requested room, check ourselves
174 # if we can access the room.
175 if not include_room and fed_room_id != queue_entry.room_id:
176 include_room = await self._is_room_accessible(
177 fed_room_id, requester, None
178 )
179
180 # The user can see the room, include it!
181 if include_room:
182 rooms_result.append(room)
183 room_ids.add(fed_room_id)
184
185 # All rooms returned don't need visiting again (even if the user
186 # didn't have access to them).
187 processed_rooms.add(fed_room_id)
188
189 for event in fed_events:
190 if event.get("room_id") in room_ids:
191 events.append(event)
192
193 logger.debug(
194 "Query of %s returned rooms %s, events %s",
195 room_id,
196 [room.get("room_id") for room in fed_rooms],
197 ["%s->%s" % (ev["room_id"], ev["state_key"]) for ev in fed_events],
198 )
136199
137200 # the room we queried may or may not have been returned, but don't process
138201 # it again, anyway.
158221 )
159222 processed_events.add(ev_key)
160223
224 # Before returning to the client, remove the allowed_spaces key for any
225 # rooms.
226 for room in rooms_result:
227 room.pop("allowed_spaces", None)
228
161229 return {"rooms": rooms_result, "events": events_result}
162230
163231 async def federation_space_summary(
164232 self,
233 origin: str,
165234 room_id: str,
166235 suggested_only: bool,
167236 max_rooms_per_space: Optional[int],
171240 Implementation of the space summary Federation API
172241
173242 Args:
243 origin: The server requesting the spaces summary.
244
174245 room_id: room id to start the summary at
175246
176247 suggested_only: whether we should only return children with the "suggested"
205276
206277 logger.debug("Processing room %s", room_id)
207278
208 rooms, events = await self._summarize_local_room(
209 None, room_id, suggested_only, max_rooms_per_space
279 room, events = await self._summarize_local_room(
280 None, origin, room_id, suggested_only, max_rooms_per_space
210281 )
211282
212283 processed_rooms.add(room_id)
213284
214 rooms_result.extend(rooms)
215 events_result.extend(events)
285 if room:
286 rooms_result.append(room)
287 events_result.extend(events)
216288
217289 # add any children to the queue
218290 room_queue.extend(edge_event["state_key"] for edge_event in events)
222294 async def _summarize_local_room(
223295 self,
224296 requester: Optional[str],
297 origin: Optional[str],
225298 room_id: str,
226299 suggested_only: bool,
227300 max_children: Optional[int],
228 ) -> Tuple[Sequence[JsonDict], Sequence[JsonDict]]:
301 ) -> Tuple[Optional[JsonDict], Sequence[JsonDict]]:
229302 """
230303 Generate a room entry and a list of event entries for a given room.
231304
232305 Args:
233 requester: The requesting user, or None if this is over federation.
306 requester:
307 The user requesting the summary, if it is a local request. None
308 if this is a federation request.
309 origin:
310 The server requesting the summary, if it is a federation request.
311 None if this is a local request.
234312 room_id: The room ID to summarize.
235313 suggested_only: True if only suggested children should be returned.
236314 Otherwise, all children are returned.
237 max_children: The maximum number of children to return for this node.
315 max_children:
316 The maximum number of children rooms to include. This is capped
317 to a server-set limit.
238318
239319 Returns:
240320 A tuple of:
243323 An iterable of the sorted children events. This may be limited
244324 to a maximum size or may include all children.
245325 """
246 if not await self._is_room_accessible(room_id, requester):
247 return (), ()
326 if not await self._is_room_accessible(room_id, requester, origin):
327 return None, ()
248328
249329 room_entry = await self._build_room_entry(room_id)
250330
268348 event_format=format_event_for_client_v2,
269349 )
270350 )
271 return (room_entry,), events_result
351 return room_entry, events_result
272352
273353 async def _summarize_remote_room(
274354 self,
277357 max_children: Optional[int],
278358 exclude_rooms: Iterable[str],
279359 ) -> Tuple[Sequence[JsonDict], Sequence[JsonDict]]:
360 """
361 Request room entries and a list of event entries for a given room by querying a remote server.
362
363 Args:
364 room: The room to summarize.
365 suggested_only: True if only suggested children should be returned.
366 Otherwise, all children are returned.
367 max_children:
368 The maximum number of children rooms to include. This is capped
369 to a server-set limit.
370 exclude_rooms:
371 Rooms IDs which do not need to be summarized.
372
373 Returns:
374 A tuple of:
375 An iterable of rooms.
376
377 An iterable of the sorted children events. This may be limited
378 to a maximum size or may include all children.
379 """
280380 room_id = room.room_id
281381 logger.info("Requesting summary for %s via %s", room_id, room.via)
282382
308408 or ev.event_type == EventTypes.SpaceChild
309409 )
310410
311 async def _is_room_accessible(self, room_id: str, requester: Optional[str]) -> bool:
312 # if we have an authenticated requesting user, first check if they are in the
313 # room
411 async def _is_room_accessible(
412 self, room_id: str, requester: Optional[str], origin: Optional[str]
413 ) -> bool:
414 """
415 Calculate whether the room should be shown in the spaces summary.
416
417 It should be included if:
418
419 * The requester is joined or invited to the room.
420 * The requester can join without an invite (per MSC3083).
421 * The origin server has any user that is joined or invited to the room.
422 * The history visibility is set to world readable.
423
424 Args:
425 room_id: The room ID to summarize.
426 requester:
427 The user requesting the summary, if it is a local request. None
428 if this is a federation request.
429 origin:
430 The server requesting the summary, if it is a federation request.
431 None if this is a local request.
432
433 Returns:
434 True if the room should be included in the spaces summary.
435 """
436 state_ids = await self._store.get_current_state_ids(room_id)
437
438 # If there's no state for the room, it isn't known.
439 if not state_ids:
440 logger.info("room %s is unknown, omitting from summary", room_id)
441 return False
442
443 room_version = await self._store.get_room_version(room_id)
444
445 # if we have an authenticated requesting user, first check if they are able to view
446 # stripped state in the room.
314447 if requester:
448 member_event_id = state_ids.get((EventTypes.Member, requester), None)
449
450 # If they're in the room they can see info on it.
451 member_event = None
452 if member_event_id:
453 member_event = await self._store.get_event(member_event_id)
454 if member_event.membership in (Membership.JOIN, Membership.INVITE):
455 return True
456
457 # Otherwise, check if they should be allowed access via membership in a space.
315458 try:
316 await self._auth.check_user_in_room(room_id, requester)
459 await self._event_auth_handler.check_restricted_join_rules(
460 state_ids, room_version, requester, member_event
461 )
462 except AuthError:
463 # The user doesn't have access due to spaces, but might have access
464 # another way. Keep trying.
465 pass
466 else:
317467 return True
318 except AuthError:
319 pass
468
469 # If this is a request over federation, check if the host is in the room or
470 # is in one of the spaces specified via the join rules.
471 elif origin:
472 if await self._auth.check_host_in_room(room_id, origin):
473 return True
474
475 # Alternately, if the host has a user in any of the spaces specified
476 # for access, then the host can see this room (and should do filtering
477 # if the requester cannot see it).
478 if await self._event_auth_handler.has_restricted_join_rules(
479 state_ids, room_version
480 ):
481 allowed_spaces = (
482 await self._event_auth_handler.get_spaces_that_allow_join(state_ids)
483 )
484 for space_id in allowed_spaces:
485 if await self._auth.check_host_in_room(space_id, origin):
486 return True
320487
321488 # otherwise, check if the room is peekable
322 hist_vis_ev = await self._state_handler.get_current_state(
323 room_id, EventTypes.RoomHistoryVisibility, ""
324 )
325 if hist_vis_ev:
489 hist_vis_event_id = state_ids.get((EventTypes.RoomHistoryVisibility, ""), None)
490 if hist_vis_event_id:
491 hist_vis_ev = await self._store.get_event(hist_vis_event_id)
326492 hist_vis = hist_vis_ev.content.get("history_visibility")
327493 if hist_vis == HistoryVisibility.WORLD_READABLE:
328494 return True
329495
330496 logger.info(
331 "room %s is unpeekable and user %s is not a member, omitting from summary",
497 "room %s is unpeekable and user %s is not a member / not allowed to join, omitting from summary",
332498 room_id,
333499 requester,
334500 )
352518 room_type = create_event.content.get(EventContentFields.ROOM_TYPE)
353519 if not room_type:
354520 room_type = create_event.content.get(EventContentFields.MSC1772_ROOM_TYPE)
521
522 room_version = await self._store.get_room_version(room_id)
523 allowed_spaces = None
524 if await self._event_auth_handler.has_restricted_join_rules(
525 current_state_ids, room_version
526 ):
527 allowed_spaces = await self._event_auth_handler.get_spaces_that_allow_join(
528 current_state_ids
529 )
355530
356531 entry = {
357532 "room_id": stats["room_id"],
366541 "guest_can_join": stats["guest_access"] == "can_join",
367542 "creation_ts": create_event.origin_server_ts,
368543 "room_type": room_type,
544 "allowed_spaces": allowed_spaces,
369545 }
370546
371547 # Filter out Nones – rather omit the field altogether
429605 return False
430606
431607
432 # Order may only contain characters in the range of \x20 (space) to \x7F (~).
433 _INVALID_ORDER_CHARS_RE = re.compile(r"[^\x20-\x7F]")
608 # Order may only contain characters in the range of \x20 (space) to \x7E (~) inclusive.
609 _INVALID_ORDER_CHARS_RE = re.compile(r"[^\x20-\x7E]")
434610
435611
436612 def _child_events_comparison_key(child: EventBase) -> Tuple[bool, Optional[str], str]:
812812 if self.deferred.called:
813813 return
814814
815 self.stream.write(data)
815 try:
816 self.stream.write(data)
817 except Exception:
818 self.deferred.errback()
819 return
820
816821 self.length += len(data)
817822 # The first time the maximum size is exceeded, error and cancel the
818823 # connection. dataReceived might be called again if data was received
1010 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1111 # See the License for the specific language governing permissions and
1212 # limitations under the License.
13 import abc
1314 import cgi
1415 import codecs
1516 import logging
1819 import typing
1920 import urllib.parse
2021 from io import BytesIO, StringIO
21 from typing import Callable, Dict, List, Optional, Tuple, Union
22 from typing import (
23 Callable,
24 Dict,
25 Generic,
26 List,
27 Optional,
28 Tuple,
29 TypeVar,
30 Union,
31 overload,
32 )
2233
2334 import attr
2435 import treq
2536 from canonicaljson import encode_canonical_json
2637 from prometheus_client import Counter
2738 from signedjson.sign import sign_json
39 from typing_extensions import Literal
2840
2941 from twisted.internet import defer
3042 from twisted.internet.error import DNSLookupError
4759 BlacklistingAgentWrapper,
4860 BlacklistingReactorWrapper,
4961 BodyExceededMaxSize,
62 ByteWriteable,
5063 encode_query_args,
5164 read_body_with_max_size,
5265 )
87100 QueryArgs = Dict[str, Union[str, List[str]]]
88101
89102
103 T = TypeVar("T")
104
105
106 class ByteParser(ByteWriteable, Generic[T], abc.ABC):
107 """A `ByteWriteable` that has an additional `finish` function that returns
108 the parsed data.
109 """
110
111 CONTENT_TYPE = abc.abstractproperty() # type: str # type: ignore
112 """The expected content type of the response, e.g. `application/json`. If
113 the content type doesn't match we fail the request.
114 """
115
116 @abc.abstractmethod
117 def finish(self) -> T:
118 """Called when response has finished streaming and the parser should
119 return the final result (or error).
120 """
121 pass
122
123
90124 @attr.s(slots=True, frozen=True)
91125 class MatrixFederationRequest:
92126 method = attr.ib(type=str)
147181 return self.json
148182
149183
150 async def _handle_json_response(
184 class JsonParser(ByteParser[Union[JsonDict, list]]):
185 """A parser that buffers the response and tries to parse it as JSON."""
186
187 CONTENT_TYPE = "application/json"
188
189 def __init__(self):
190 self._buffer = StringIO()
191 self._binary_wrapper = BinaryIOWrapper(self._buffer)
192
193 def write(self, data: bytes) -> int:
194 return self._binary_wrapper.write(data)
195
196 def finish(self) -> Union[JsonDict, list]:
197 return json_decoder.decode(self._buffer.getvalue())
198
199
200 async def _handle_response(
151201 reactor: IReactorTime,
152202 timeout_sec: float,
153203 request: MatrixFederationRequest,
154204 response: IResponse,
155205 start_ms: int,
156 ) -> JsonDict:
157 """
158 Reads the JSON body of a response, with a timeout
206 parser: ByteParser[T],
207 max_response_size: Optional[int] = None,
208 ) -> T:
209 """
210 Reads the body of a response with a timeout and sends it to a parser
159211
160212 Args:
161213 reactor: twisted reactor, for the timeout
163215 request: the request that triggered the response
164216 response: response to the request
165217 start_ms: Timestamp when request was made
218 parser: The parser for the response
219 max_response_size: The maximum size to read from the response, if None
220 uses the default.
166221
167222 Returns:
168 The parsed JSON response
169 """
223 The parsed response
224 """
225
226 if max_response_size is None:
227 max_response_size = MAX_RESPONSE_SIZE
228
170229 try:
171 check_content_type_is_json(response.headers)
172
173 buf = StringIO()
174 d = read_body_with_max_size(response, BinaryIOWrapper(buf), MAX_RESPONSE_SIZE)
230 check_content_type_is(response.headers, parser.CONTENT_TYPE)
231
232 d = read_body_with_max_size(response, parser, max_response_size)
175233 d = timeout_deferred(d, timeout=timeout_sec, reactor=reactor)
176234
177 def parse(_len: int):
178 return json_decoder.decode(buf.getvalue())
179
180 d.addCallback(parse)
181
182 body = await make_deferred_yieldable(d)
235 length = await make_deferred_yieldable(d)
236
237 value = parser.finish()
183238 except BodyExceededMaxSize as e:
184239 # The response was too big.
185240 logger.warning(
192247 )
193248 raise RequestSendFailed(e, can_retry=False) from e
194249 except ValueError as e:
195 # The JSON content was invalid.
250 # The content was invalid.
196251 logger.warning(
197 "{%s} [%s] Failed to parse JSON response - %s %s",
252 "{%s} [%s] Failed to parse response - %s %s",
198253 request.txn_id,
199254 request.destination,
200255 request.method,
224279 time_taken_secs = reactor.seconds() - start_ms / 1000
225280
226281 logger.info(
227 "{%s} [%s] Completed request: %d %s in %.2f secs - %s %s",
282 "{%s} [%s] Completed request: %d %s in %.2f secs, got %d bytes - %s %s",
228283 request.txn_id,
229284 request.destination,
230285 response.code,
231286 response.phrase.decode("ascii", errors="replace"),
232287 time_taken_secs,
288 length,
233289 request.method,
234290 request.uri.decode("ascii"),
235291 )
236 return body
292 return value
237293
238294
239295 class BinaryIOWrapper:
670726 )
671727 return auth_headers
672728
729 @overload
673730 async def put_json(
674731 self,
675732 destination: str,
682739 ignore_backoff: bool = False,
683740 backoff_on_404: bool = False,
684741 try_trailing_slash_on_400: bool = False,
742 parser: Literal[None] = None,
743 max_response_size: Optional[int] = None,
685744 ) -> Union[JsonDict, list]:
745 ...
746
747 @overload
748 async def put_json(
749 self,
750 destination: str,
751 path: str,
752 args: Optional[QueryArgs] = None,
753 data: Optional[JsonDict] = None,
754 json_data_callback: Optional[Callable[[], JsonDict]] = None,
755 long_retries: bool = False,
756 timeout: Optional[int] = None,
757 ignore_backoff: bool = False,
758 backoff_on_404: bool = False,
759 try_trailing_slash_on_400: bool = False,
760 parser: Optional[ByteParser[T]] = None,
761 max_response_size: Optional[int] = None,
762 ) -> T:
763 ...
764
765 async def put_json(
766 self,
767 destination: str,
768 path: str,
769 args: Optional[QueryArgs] = None,
770 data: Optional[JsonDict] = None,
771 json_data_callback: Optional[Callable[[], JsonDict]] = None,
772 long_retries: bool = False,
773 timeout: Optional[int] = None,
774 ignore_backoff: bool = False,
775 backoff_on_404: bool = False,
776 try_trailing_slash_on_400: bool = False,
777 parser: Optional[ByteParser] = None,
778 max_response_size: Optional[int] = None,
779 ):
686780 """Sends the specified json data using PUT
687781
688782 Args:
715809 of the request. Workaround for #3622 in Synapse <= v0.99.3. This
716810 will be attempted before backing off if backing off has been
717811 enabled.
812 parser: The parser to use to decode the response. Defaults to
813 parsing as JSON.
814 max_response_size: The maximum size to read from the response, if None
815 uses the default.
718816
719817 Returns:
720818 Succeeds when we get a 2xx HTTP response. The
755853 else:
756854 _sec_timeout = self.default_timeout
757855
758 body = await _handle_json_response(
759 self.reactor, _sec_timeout, request, response, start_ms
856 if parser is None:
857 parser = JsonParser()
858
859 body = await _handle_response(
860 self.reactor,
861 _sec_timeout,
862 request,
863 response,
864 start_ms,
865 parser=parser,
866 max_response_size=max_response_size,
760867 )
761868
762869 return body
829936 else:
830937 _sec_timeout = self.default_timeout
831938
832 body = await _handle_json_response(
833 self.reactor,
834 _sec_timeout,
835 request,
836 response,
837 start_ms,
939 body = await _handle_response(
940 self.reactor, _sec_timeout, request, response, start_ms, parser=JsonParser()
838941 )
839942 return body
840943
9061009 else:
9071010 _sec_timeout = self.default_timeout
9081011
909 body = await _handle_json_response(
910 self.reactor, _sec_timeout, request, response, start_ms
1012 body = await _handle_response(
1013 self.reactor, _sec_timeout, request, response, start_ms, parser=JsonParser()
9111014 )
9121015
9131016 return body
9741077 else:
9751078 _sec_timeout = self.default_timeout
9761079
977 body = await _handle_json_response(
978 self.reactor, _sec_timeout, request, response, start_ms
1080 body = await _handle_response(
1081 self.reactor, _sec_timeout, request, response, start_ms, parser=JsonParser()
9791082 )
9801083 return body
9811084
10671170 return repr(e)
10681171
10691172
1070 def check_content_type_is_json(headers: Headers) -> None:
1173 def check_content_type_is(headers: Headers, expected_content_type: str) -> None:
10711174 """
10721175 Check that a set of HTTP headers have a Content-Type header, and that it
1073 is application/json.
1176 is the expected value..
10741177
10751178 Args:
10761179 headers: headers to check
10771180
10781181 Raises:
1079 RequestSendFailed: if the Content-Type header is missing or isn't JSON
1182 RequestSendFailed: if the Content-Type header is missing or doesn't match
10801183
10811184 """
10821185 content_type_headers = headers.getRawHeaders(b"Content-Type")
10881191
10891192 c_type = content_type_headers[0].decode("ascii") # only the first header
10901193 val, options = cgi.parse_header(c_type)
1091 if val != "application/json":
1194 if val != expected_content_type:
10921195 raise RequestSendFailed(
10931196 RuntimeError(
1094 "Remote server sent Content-Type header of '%s', not 'application/json'"
1095 % c_type,
1197 f"Remote server sent Content-Type header of '{c_type}', not '{expected_content_type}'",
10961198 ),
10971199 can_retry=False,
10981200 )
104104 assert self.content, "handleContentChunk() called before gotLength()"
105105 if self.content.tell() + len(data) > self._max_request_body_size:
106106 logger.warning(
107 "Aborting connection from %s because the request exceeds maximum size",
107 "Aborting connection from %s because the request exceeds maximum size: %s %s",
108108 self.client,
109 self.get_method(),
110 self.get_redacted_uri(),
109111 )
110112 self.transport.abortConnection()
111113 return
5555 self._http_client = hs.get_simple_http_client() # type: SimpleHttpClient
5656 self._public_room_list_manager = PublicRoomListManager(hs)
5757
58 # The next time these users sync, they will receive the current presence
59 # state of all local users. Users are added by send_local_online_presence_to,
60 # and removed after a successful sync.
61 #
62 # We make this a private variable to deter modules from accessing it directly,
63 # though other classes in Synapse will still do so.
64 self._send_full_presence_to_local_users = set()
65
6658 @property
6759 def http_client(self):
6860 """Allows making outbound HTTP requests to remote resources.
404396 Updates to remote users will be sent immediately, whereas local users will receive
405397 them on their next sync attempt.
406398
407 Note that this method can only be run on the main or federation_sender worker
408 processes.
409 """
410 if not self._hs.should_send_federation():
399 Note that this method can only be run on the process that is configured to write to the
400 presence stream. By default this is the main process.
401 """
402 if self._hs._instance_name not in self._hs.config.worker.writers.presence:
411403 raise Exception(
412404 "send_local_online_presence_to can only be run "
413 "on processes that send federation",
414 )
415
405 "on the process that is configured to write to the "
406 "presence stream (by default this is the main process)",
407 )
408
409 local_users = set()
410 remote_users = set()
416411 for user in users:
417412 if self._hs.is_mine_id(user):
418 # Modify SyncHandler._generate_sync_entry_for_presence to call
419 # presence_source.get_new_events with an empty `from_key` if
420 # that user's ID were in a list modified by ModuleApi somewhere.
421 # That user would then get all presence state on next incremental sync.
422
423 # Force a presence initial_sync for this user next time
424 self._send_full_presence_to_local_users.add(user)
413 local_users.add(user)
425414 else:
426 # Retrieve presence state for currently online users that this user
427 # is considered interested in
428 presence_events, _ = await self._presence_stream.get_new_events(
429 UserID.from_string(user), from_key=None, include_offline=False
430 )
431
432 # Send to remote destinations.
433
434 # We pull out the presence handler here to break a cyclic
435 # dependency between the presence router and module API.
436 presence_handler = self._hs.get_presence_handler()
437 await presence_handler.maybe_send_presence_to_interested_destinations(
438 presence_events
439 )
415 remote_users.add(user)
416
417 # We pull out the presence handler here to break a cyclic
418 # dependency between the presence router and module API.
419 presence_handler = self._hs.get_presence_handler()
420
421 if local_users:
422 # Force a presence initial_sync for these users next time they sync.
423 await presence_handler.send_full_presence_to_users(local_users)
424
425 for user in remote_users:
426 # Retrieve presence state for currently online users that this user
427 # is considered interested in.
428 presence_events, _ = await self._presence_stream.get_new_events(
429 UserID.from_string(user), from_key=None, include_offline=False
430 )
431
432 # Send to remote destinations.
433 destination = UserID.from_string(user).domain
434 presence_handler.get_federation_queue().send_presence_to_destinations(
435 presence_events, destination
436 )
440437
441438
442439 class PublicRoomListManager:
1111 # See the License for the specific language governing permissions and
1212 # limitations under the License.
1313
14 import email.mime.multipart
15 import email.utils
1614 import logging
1715 import urllib.parse
18 from email.mime.multipart import MIMEMultipart
19 from email.mime.text import MIMEText
2016 from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, TypeVar
2117
2218 import bleach
2622 from synapse.api.errors import StoreError
2723 from synapse.config.emailconfig import EmailSubjectConfig
2824 from synapse.events import EventBase
29 from synapse.logging.context import make_deferred_yieldable
3025 from synapse.push.presentable_names import (
3126 calculate_room_name,
3227 descriptor_from_member_events,
107102 self.template_html = template_html
108103 self.template_text = template_text
109104
110 self.sendmail = self.hs.get_sendmail()
105 self.send_email_handler = hs.get_send_email_handler()
111106 self.store = self.hs.get_datastore()
112107 self.state_store = self.hs.get_storage().state
113108 self.macaroon_gen = self.hs.get_macaroon_generator()
309304 self, email_address: str, subject: str, extra_template_vars: Dict[str, Any]
310305 ) -> None:
311306 """Send an email with the given information and template text"""
312 try:
313 from_string = self.hs.config.email_notif_from % {"app": self.app_name}
314 except TypeError:
315 from_string = self.hs.config.email_notif_from
316
317 raw_from = email.utils.parseaddr(from_string)[1]
318 raw_to = email.utils.parseaddr(email_address)[1]
319
320 if raw_to == "":
321 raise RuntimeError("Invalid 'to' address")
322
323307 template_vars = {
324308 "app_name": self.app_name,
325309 "server_name": self.hs.config.server.server_name,
328312 template_vars.update(extra_template_vars)
329313
330314 html_text = self.template_html.render(**template_vars)
331 html_part = MIMEText(html_text, "html", "utf8")
332
333315 plain_text = self.template_text.render(**template_vars)
334 text_part = MIMEText(plain_text, "plain", "utf8")
335
336 multipart_msg = MIMEMultipart("alternative")
337 multipart_msg["Subject"] = subject
338 multipart_msg["From"] = from_string
339 multipart_msg["To"] = email_address
340 multipart_msg["Date"] = email.utils.formatdate()
341 multipart_msg["Message-ID"] = email.utils.make_msgid()
342 multipart_msg.attach(text_part)
343 multipart_msg.attach(html_part)
344
345 logger.info("Sending email to %s" % email_address)
346
347 await make_deferred_yieldable(
348 self.sendmail(
349 self.hs.config.email_smtp_host,
350 raw_from,
351 raw_to,
352 multipart_msg.as_string().encode("utf8"),
353 reactor=self.hs.get_reactor(),
354 port=self.hs.config.email_smtp_port,
355 requireAuthentication=self.hs.config.email_smtp_user is not None,
356 username=self.hs.config.email_smtp_user,
357 password=self.hs.config.email_smtp_pass,
358 requireTransportSecurity=self.hs.config.require_transport_security,
359 )
316
317 await self.send_email_handler.send_email(
318 email_address=email_address,
319 subject=subject,
320 app_name=self.app_name,
321 html=html_text,
322 text=plain_text,
360323 )
361324
362325 async def _get_room_vars(
8686 # We enforce that we have a `cryptography` version that bundles an `openssl`
8787 # with the latest security patches.
8888 "cryptography>=3.4.7",
89 "ijson>=3.0",
8990 ]
9091
9192 CONDITIONAL_REQUIREMENTS = {
7272 {
7373 "state": { ... },
7474 "ignore_status_msg": false,
75 "force_notify": false
7576 }
7677
7778 200 OK
9091 self._presence_handler = hs.get_presence_handler()
9192
9293 @staticmethod
93 async def _serialize_payload(user_id, state, ignore_status_msg=False):
94 async def _serialize_payload(
95 user_id, state, ignore_status_msg=False, force_notify=False
96 ):
9497 return {
9598 "state": state,
9699 "ignore_status_msg": ignore_status_msg,
100 "force_notify": force_notify,
97101 }
98102
99103 async def _handle_request(self, request, user_id):
100104 content = parse_json_object_from_request(request)
101105
102106 await self._presence_handler.set_state(
103 UserID.from_string(user_id), content["state"], content["ignore_status_msg"]
107 UserID.from_string(user_id),
108 content["state"],
109 content["ignore_status_msg"],
110 content["force_notify"],
104111 )
105112
106113 return (
2323 super().__init__(database, db_conn, hs)
2424
2525 self.client_ip_last_seen = LruCache(
26 cache_name="client_ip_last_seen", keylen=4, max_size=50000
26 cache_name="client_ip_last_seen", max_size=50000
2727 ) # type: LruCache[tuple, int]
2828
2929 async def insert_client_ip(self, user_id, access_token, ip, user_agent, device_id):
+0
-21
synapse/replication/slave/storage/transactions.py less more
0 # Copyright 2015, 2016 OpenMarket Ltd
1 #
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 from synapse.storage.databases.main.transactions import TransactionStore
15
16 from ._base import BaseSlavedStore
17
18
19 class SlavedTransactionStore(TransactionStore, BaseSlavedStore):
20 pass
5353 self.hs = hs
5454 self.auth = hs.get_auth()
5555 self.txns = HttpTransactionCache(hs)
56 self.snm = hs.get_server_notices_manager()
5756
5857 def register(self, json_resource: HttpServer):
5958 PATTERN = "/send_server_notice"
7675 event_type = body.get("type", EventTypes.Message)
7776 state_key = body.get("state_key")
7877
79 if not self.snm.is_enabled():
78 # We grab the server notices manager here as its initialisation has a check for worker processes,
79 # but worker processes still need to initialise SendServerNoticeServlet (as it is part of the
80 # admin api).
81 if not self.hs.get_server_notices_manager().is_enabled():
8082 raise SynapseError(400, "Server notices are not enabled on this server")
8183
8284 user_id = body["user_id"]
8486 if not self.hs.is_mine_id(user_id):
8587 raise SynapseError(400, "Server notices can only be sent to local users")
8688
87 event = await self.snm.send_notice(
89 event = await self.hs.get_server_notices_manager().send_notice(
8890 user_id=body["user_id"],
8991 type=event_type,
9092 state_key=state_key,
4747 "key": # base64 encoded NACL verification key.
4848 }
4949 },
50 "tls_fingerprints": [ # Fingerprints of the TLS certs this server uses.
51 {
52 "sha256": # base64 encoded sha256 fingerprint of the X509 cert
53 },
54 ],
5550 "signatures": {
5651 "this.server.example.com": {
5752 "algorithm:version": # NACL signature for this server
8883 "expired_ts": key.expired_ts,
8984 }
9085
91 tls_fingerprints = self.config.tls_fingerprints
92
9386 json_object = {
9487 "valid_until_ts": self.valid_until_ts,
9588 "server_name": self.config.server_name,
9689 "verify_keys": verify_keys,
9790 "old_verify_keys": old_verify_keys,
98 "tls_fingerprints": tls_fingerprints,
9991 }
10092 for key in self.config.signing_key:
10193 json_object = sign_json(json_object, self.config.server_name, key)
7272 "expired_ts": 0, # when the key stop being used.
7373 }
7474 }
75 "tls_fingerprints": [
76 { "sha256": # fingerprint }
77 ]
7875 "signatures": {
7976 "remote.server.example.com": {...}
8077 "this.server.example.com": {...}
7474 self.store = hs.get_datastore()
7575 self.max_upload_size = hs.config.max_upload_size
7676 self.max_image_pixels = hs.config.max_image_pixels
77
78 Thumbnailer.set_limits(self.max_image_pixels)
7779
7880 self.primary_base_path = hs.config.media_store_path # type: str
7981 self.filepaths = MediaFilePaths(self.primary_base_path) # type: MediaFilePaths
3939
4040 FORMATS = {"image/jpeg": "JPEG", "image/png": "PNG"}
4141
42 @staticmethod
43 def set_limits(max_image_pixels: int):
44 Image.MAX_IMAGE_PIXELS = max_image_pixels
45
4246 def __init__(self, input_path: str):
4347 try:
4448 self.image = Image.open(input_path)
4549 except OSError as e:
4650 # If an error occurs opening the image, a thumbnail won't be able to
4751 # be generated.
52 raise ThumbnailError from e
53 except Image.DecompressionBombError as e:
54 # If an image decompression bomb error occurs opening the image,
55 # then the image exceeds the pixel limit and a thumbnail won't
56 # be able to be generated.
4857 raise ThumbnailError from e
4958
5059 self.width, self.height = self.image.size
103103 from synapse.handlers.room_member import RoomMemberHandler, RoomMemberMasterHandler
104104 from synapse.handlers.room_member_worker import RoomMemberWorkerHandler
105105 from synapse.handlers.search import SearchHandler
106 from synapse.handlers.send_email import SendEmailHandler
106107 from synapse.handlers.set_password import SetPasswordHandler
107108 from synapse.handlers.space_summary import SpaceSummaryHandler
108109 from synapse.handlers.sso import SsoHandler
549550 return SearchHandler(self)
550551
551552 @cache_in_self
553 def get_send_email_handler(self) -> SendEmailHandler:
554 return SendEmailHandler(self)
555
556 @cache_in_self
552557 def get_set_password_handler(self) -> SetPasswordHandler:
553558 return SetPasswordHandler(self)
554559
1313 # See the License for the specific language governing permissions and
1414 # limitations under the License.
1515 import logging
16 import random
1716 from abc import ABCMeta
1817 from typing import TYPE_CHECKING, Any, Collection, Iterable, Optional, Union
1918
4342 self._clock = hs.get_clock()
4443 self.database_engine = database.engine
4544 self.db_pool = database
46 self.rand = random.SystemRandom()
4745
4846 def process_replication_rows(
4947 self,
6666 from .stats import StatsStore
6767 from .stream import StreamStore
6868 from .tags import TagsStore
69 from .transactions import TransactionStore
69 from .transactions import TransactionWorkerStore
7070 from .ui_auth import UIAuthStore
7171 from .user_directory import UserDirectoryStore
7272 from .user_erasure_store import UserErasureStore
8282 StreamStore,
8383 ProfileStore,
8484 PresenceStore,
85 TransactionStore,
85 TransactionWorkerStore,
8686 DirectoryStore,
8787 KeyStore,
8888 StateStore,
435435 def __init__(self, database: DatabasePool, db_conn, hs):
436436
437437 self.client_ip_last_seen = LruCache(
438 cache_name="client_ip_last_seen", keylen=4, max_size=50000
438 cache_name="client_ip_last_seen", max_size=50000
439439 )
440440
441441 super().__init__(database, db_conn, hs)
664664 cached_method_name="get_device_list_last_stream_id_for_remote",
665665 list_name="user_ids",
666666 )
667 async def get_device_list_last_stream_id_for_remotes(self, user_ids: str):
667 async def get_device_list_last_stream_id_for_remotes(self, user_ids: Iterable[str]):
668668 rows = await self.db_pool.simple_select_many_batch(
669669 table="device_lists_remote_extremeties",
670670 column="user_id",
10521052 # Map of (user_id, device_id) -> bool. If there is an entry that implies
10531053 # the device exists.
10541054 self.device_id_exists_cache = LruCache(
1055 cache_name="device_id_exists", keylen=2, max_size=10000
1055 cache_name="device_id_exists", max_size=10000
10561056 )
10571057
10581058 async def store_device(
472472 num_args=1,
473473 )
474474 async def _get_bare_e2e_cross_signing_keys_bulk(
475 self, user_ids: List[str]
475 self, user_ids: Iterable[str]
476476 ) -> Dict[str, Dict[str, dict]]:
477477 """Returns the cross-signing keys for a set of users. The output of this
478478 function should be passed to _get_e2e_cross_signing_signatures_txn if
496496 def _get_bare_e2e_cross_signing_keys_bulk_txn(
497497 self,
498498 txn: Connection,
499 user_ids: List[str],
499 user_ids: Iterable[str],
500500 ) -> Dict[str, Dict[str, dict]]:
501501 """Returns the cross-signing keys for a set of users. The output of this
502502 function should be passed to _get_e2e_cross_signing_signatures_txn if
156156
157157 self._get_event_cache = LruCache(
158158 cache_name="*getEvent*",
159 keylen=3,
160159 max_size=hs.config.caches.event_cache_size,
161160 )
162161
5454 """
5555 keys = {}
5656
57 def _get_keys(txn: Cursor, batch: Tuple[Tuple[str, str]]) -> None:
57 def _get_keys(txn: Cursor, batch: Tuple[Tuple[str, str], ...]) -> None:
5858 """Processes a batch of keys to fetch, and adds the result to `keys`."""
5959
6060 # batch_iter always returns tuples so it's safe to do len(batch)
1111 # See the License for the specific language governing permissions and
1212 # limitations under the License.
1313
14 from typing import TYPE_CHECKING, Dict, List, Tuple
14 from typing import TYPE_CHECKING, Dict, Iterable, List, Tuple
1515
1616 from synapse.api.presence import PresenceState, UserPresenceState
1717 from synapse.replication.tcp.streams import PresenceStream
5656 db_conn, "presence_stream", "stream_id"
5757 )
5858
59 self.hs = hs
5960 self._presence_on_startup = self._get_active_presence(db_conn)
6061
6162 presence_cache_prefill, min_presence_val = self.db_pool.get_cache_dict(
9495 self.presence_stream_cache.entity_has_changed, state.user_id, stream_id
9596 )
9697 txn.call_after(self._get_presence_for_user.invalidate, (state.user_id,))
98
99 # Delete old rows to stop database from getting really big
100 sql = "DELETE FROM presence_stream WHERE stream_id < ? AND "
101
102 for states in batch_iter(presence_states, 50):
103 clause, args = make_in_list_sql_clause(
104 self.database_engine, "user_id", [s.user_id for s in states]
105 )
106 txn.execute(sql + clause, [stream_id] + list(args))
97107
98108 # Actually insert new rows
99109 self.db_pool.simple_insert_many_txn(
114124 for stream_id, state in zip(stream_orderings, presence_states)
115125 ],
116126 )
117
118 # Delete old rows to stop database from getting really big
119 sql = "DELETE FROM presence_stream WHERE stream_id < ? AND "
120
121 for states in batch_iter(presence_states, 50):
122 clause, args = make_in_list_sql_clause(
123 self.database_engine, "user_id", [s.user_id for s in states]
124 )
125 txn.execute(sql + clause, [stream_id] + list(args))
126127
127128 async def get_all_presence_updates(
128129 self, instance_name: str, last_id: int, current_id: int, limit: int
208209 row["currently_active"] = bool(row["currently_active"])
209210
210211 return {row["user_id"]: UserPresenceState(**row) for row in rows}
212
213 async def should_user_receive_full_presence_with_token(
214 self,
215 user_id: str,
216 from_token: int,
217 ) -> bool:
218 """Check whether the given user should receive full presence using the stream token
219 they're updating from.
220
221 Args:
222 user_id: The ID of the user to check.
223 from_token: The stream token included in their /sync token.
224
225 Returns:
226 True if the user should have full presence sent to them, False otherwise.
227 """
228
229 def _should_user_receive_full_presence_with_token_txn(txn):
230 sql = """
231 SELECT 1 FROM users_to_send_full_presence_to
232 WHERE user_id = ?
233 AND presence_stream_id >= ?
234 """
235 txn.execute(sql, (user_id, from_token))
236 return bool(txn.fetchone())
237
238 return await self.db_pool.runInteraction(
239 "should_user_receive_full_presence_with_token",
240 _should_user_receive_full_presence_with_token_txn,
241 )
242
243 async def add_users_to_send_full_presence_to(self, user_ids: Iterable[str]):
244 """Adds to the list of users who should receive a full snapshot of presence
245 upon their next sync.
246
247 Args:
248 user_ids: An iterable of user IDs.
249 """
250 # Add user entries to the table, updating the presence_stream_id column if the user already
251 # exists in the table.
252 await self.db_pool.simple_upsert_many(
253 table="users_to_send_full_presence_to",
254 key_names=("user_id",),
255 key_values=[(user_id,) for user_id in user_ids],
256 value_names=("presence_stream_id",),
257 # We save the current presence stream ID token along with the user ID entry so
258 # that when a user /sync's, even if they syncing multiple times across separate
259 # devices at different times, each device will receive full presence once - when
260 # the presence stream ID in their sync token is less than the one in the table
261 # for their user ID.
262 value_values=(
263 (self._presence_id_gen.get_current_token(),) for _ in user_ids
264 ),
265 desc="add_users_to_send_full_presence_to",
266 )
211267
212268 async def get_presence_for_all_users(
213269 self,
1313 # See the License for the specific language governing permissions and
1414 # limitations under the License.
1515 import logging
16 import random
1617 import re
1718 from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
1819
996997 expiration_ts = now_ms + self._account_validity_period
997998
998999 if use_delta:
999 expiration_ts = self.rand.randrange(
1000 expiration_ts = random.randrange(
10001001 expiration_ts - self._account_validity_startup_job_max_delta,
10011002 expiration_ts,
10021003 )
1515 from collections import namedtuple
1616 from typing import Iterable, List, Optional, Tuple
1717
18 import attr
1819 from canonicaljson import encode_canonical_json
1920
2021 from synapse.metrics.background_process_metrics import wrap_as_background_process
21 from synapse.storage._base import SQLBaseStore, db_to_json
22 from synapse.storage._base import db_to_json
2223 from synapse.storage.database import DatabasePool, LoggingTransaction
24 from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore
2325 from synapse.types import JsonDict
24 from synapse.util.caches.expiringcache import ExpiringCache
26 from synapse.util.caches.descriptors import cached
2527
2628 db_binary_type = memoryview
2729
3739 "_TransactionRow", ("response_code", "response_json")
3840 )
3941
40 SENTINEL = object()
41
42
43 class TransactionWorkerStore(SQLBaseStore):
42
43 @attr.s(slots=True, frozen=True, auto_attribs=True)
44 class DestinationRetryTimings:
45 """The current destination retry timing info for a remote server."""
46
47 # The first time we tried and failed to reach the remote server, in ms.
48 failure_ts: int
49
50 # The last time we tried and failed to reach the remote server, in ms.
51 retry_last_ts: int
52
53 # How long since the last time we tried to reach the remote server before
54 # trying again, in ms.
55 retry_interval: int
56
57
58 class TransactionWorkerStore(CacheInvalidationWorkerStore):
4459 def __init__(self, database: DatabasePool, db_conn, hs):
4560 super().__init__(database, db_conn, hs)
4661
5772
5873 await self.db_pool.runInteraction(
5974 "_cleanup_transactions", _cleanup_transactions_txn
60 )
61
62
63 class TransactionStore(TransactionWorkerStore):
64 """A collection of queries for handling PDUs."""
65
66 def __init__(self, database: DatabasePool, db_conn, hs):
67 super().__init__(database, db_conn, hs)
68
69 self._destination_retry_cache = ExpiringCache(
70 cache_name="get_destination_retry_timings",
71 clock=self._clock,
72 expiry_ms=5 * 60 * 1000,
7375 )
7476
7577 async def get_received_txn_response(
144146 desc="set_received_txn_response",
145147 )
146148
147 async def get_destination_retry_timings(self, destination):
149 @cached(max_entries=10000)
150 async def get_destination_retry_timings(
151 self,
152 destination: str,
153 ) -> Optional[DestinationRetryTimings]:
148154 """Gets the current retry timings (if any) for a given destination.
149155
150156 Args:
154160 None if not retrying
155161 Otherwise a dict for the retry scheme
156162 """
157
158 result = self._destination_retry_cache.get(destination, SENTINEL)
159 if result is not SENTINEL:
160 return result
161163
162164 result = await self.db_pool.runInteraction(
163165 "get_destination_retry_timings",
165167 destination,
166168 )
167169
168 # We don't hugely care about race conditions between getting and
169 # invalidating the cache, since we time out fairly quickly anyway.
170 self._destination_retry_cache[destination] = result
171170 return result
172171
173 def _get_destination_retry_timings(self, txn, destination):
172 def _get_destination_retry_timings(
173 self, txn, destination: str
174 ) -> Optional[DestinationRetryTimings]:
174175 result = self.db_pool.simple_select_one_txn(
175176 txn,
176177 table="destinations",
177178 keyvalues={"destination": destination},
178 retcols=("destination", "failure_ts", "retry_last_ts", "retry_interval"),
179 retcols=("failure_ts", "retry_last_ts", "retry_interval"),
179180 allow_none=True,
180181 )
181182
182183 # check we have a row and retry_last_ts is not null or zero
183184 # (retry_last_ts can't be negative)
184185 if result and result["retry_last_ts"]:
185 return result
186 return DestinationRetryTimings(**result)
186187 else:
187188 return None
188189
203204 retry_interval: how long until next retry in ms
204205 """
205206
206 self._destination_retry_cache.pop(destination, None)
207207 if self.database_engine.can_native_upsert:
208208 return await self.db_pool.runInteraction(
209209 "set_destination_retry_timings",
250250 """
251251
252252 txn.execute(sql, (destination, failure_ts, retry_last_ts, retry_interval))
253
254 self._invalidate_cache_and_stream(
255 txn, self.get_destination_retry_timings, (destination,)
256 )
253257
254258 def _set_destination_retry_timings_emulated(
255259 self, txn, destination, failure_ts, retry_last_ts, retry_interval
294298 },
295299 )
296300
301 self._invalidate_cache_and_stream(
302 txn, self.get_destination_retry_timings, (destination,)
303 )
304
297305 async def store_destination_rooms_entries(
298306 self,
299307 destinations: Iterable[str],
1010 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1111 # See the License for the specific language governing permissions and
1212 # limitations under the License.
13
14 from typing import Dict, Iterable
1315
1416 from synapse.storage._base import SQLBaseStore
1517 from synapse.util.caches.descriptors import cached, cachedList
3638 return bool(result)
3739
3840 @cachedList(cached_method_name="is_user_erased", list_name="user_ids")
39 async def are_users_erased(self, user_ids):
41 async def are_users_erased(self, user_ids: Iterable[str]) -> Dict[str, bool]:
4042 """
4143 Checks which users in a list have requested erasure
4244
4345 Args:
44 user_ids (iterable[str]): full user id to check
46 user_ids: full user ids to check
4547
4648 Returns:
47 dict[str, bool]:
48 for each user, whether the user has requested erasure.
49 for each user, whether the user has requested erasure.
4950 """
50 # this serves the dual purpose of (a) making sure we can do len and
51 # iterate it multiple times, and (b) avoiding duplicates.
52 user_ids = tuple(set(user_ids))
53
5451 rows = await self.db_pool.simple_select_many_batch(
5552 table="erased_users",
5653 column="user_id",
0 /* Copyright 2021 The Matrix.org Foundation C.I.C
1 *
2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * http://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14
15 -- Add a table that keeps track of a list of users who should, upon their next
16 -- sync request, receive presence for all currently online users that they are
17 -- "interested" in.
18
19 -- The motivation for a DB table over an in-memory list is so that this list
20 -- can be added to and retrieved from by any worker. Specifically, we don't
21 -- want to duplicate work across multiple sync workers.
22
23 CREATE TABLE IF NOT EXISTS users_to_send_full_presence_to(
24 -- The user ID to send full presence to.
25 user_id TEXT PRIMARY KEY,
26 -- A presence stream ID token - the current presence stream token when the row was last upserted.
27 -- If a user calls /sync and this token is part of the update they're to receive, we also include
28 -- full user presence in the response.
29 -- This allows multiple devices for a user to receive full presence whenever they next call /sync.
30 presence_stream_id BIGINT,
31 FOREIGN KEY (user_id)
32 REFERENCES users (name)
33 );⏎
539539 state_filter: The state filter used to fetch state from the database.
540540
541541 Returns:
542 A dict from (type, state_key) -> state_event
542 A dict from (type, state_key) -> state_event_id
543543 """
544544 state_map = await self.get_state_ids_for_events(
545545 [event_id], state_filter or StateFilter.all()
0 # Copyright 2021 The Matrix.org Foundation C.I.C.
1 #
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 import logging
15 from typing import (
16 Awaitable,
17 Callable,
18 Dict,
19 Generic,
20 Hashable,
21 List,
22 Set,
23 Tuple,
24 TypeVar,
25 )
26
27 from twisted.internet import defer
28
29 from synapse.logging.context import PreserveLoggingContext, make_deferred_yieldable
30 from synapse.metrics import LaterGauge
31 from synapse.metrics.background_process_metrics import run_as_background_process
32 from synapse.util import Clock
33
34 logger = logging.getLogger(__name__)
35
36
37 V = TypeVar("V")
38 R = TypeVar("R")
39
40
41 class BatchingQueue(Generic[V, R]):
42 """A queue that batches up work, calling the provided processing function
43 with all pending work (for a given key).
44
45 The provided processing function will only be called once at a time for each
46 key. It will be called the next reactor tick after `add_to_queue` has been
47 called, and will keep being called until the queue has been drained (for the
48 given key).
49
50 Note that the return value of `add_to_queue` will be the return value of the
51 processing function that processed the given item. This means that the
52 returned value will likely include data for other items that were in the
53 batch.
54 """
55
56 def __init__(
57 self,
58 name: str,
59 clock: Clock,
60 process_batch_callback: Callable[[List[V]], Awaitable[R]],
61 ):
62 self._name = name
63 self._clock = clock
64
65 # The set of keys currently being processed.
66 self._processing_keys = set() # type: Set[Hashable]
67
68 # The currently pending batch of values by key, with a Deferred to call
69 # with the result of the corresponding `_process_batch_callback` call.
70 self._next_values = {} # type: Dict[Hashable, List[Tuple[V, defer.Deferred]]]
71
72 # The function to call with batches of values.
73 self._process_batch_callback = process_batch_callback
74
75 LaterGauge(
76 "synapse_util_batching_queue_number_queued",
77 "The number of items waiting in the queue across all keys",
78 labels=("name",),
79 caller=lambda: sum(len(v) for v in self._next_values.values()),
80 )
81
82 LaterGauge(
83 "synapse_util_batching_queue_number_of_keys",
84 "The number of distinct keys that have items queued",
85 labels=("name",),
86 caller=lambda: len(self._next_values),
87 )
88
89 async def add_to_queue(self, value: V, key: Hashable = ()) -> R:
90 """Adds the value to the queue with the given key, returning the result
91 of the processing function for the batch that included the given value.
92
93 The optional `key` argument allows sharding the queue by some key. The
94 queues will then be processed in parallel, i.e. the process batch
95 function will be called in parallel with batched values from a single
96 key.
97 """
98
99 # First we create a defer and add it and the value to the list of
100 # pending items.
101 d = defer.Deferred()
102 self._next_values.setdefault(key, []).append((value, d))
103
104 # If we're not currently processing the key fire off a background
105 # process to start processing.
106 if key not in self._processing_keys:
107 run_as_background_process(self._name, self._process_queue, key)
108
109 return await make_deferred_yieldable(d)
110
111 async def _process_queue(self, key: Hashable) -> None:
112 """A background task to repeatedly pull things off the queue for the
113 given key and call the `self._process_batch_callback` with the values.
114 """
115
116 try:
117 if key in self._processing_keys:
118 return
119
120 self._processing_keys.add(key)
121
122 while True:
123 # We purposefully wait a reactor tick to allow us to batch
124 # together requests that we're about to receive. A common
125 # pattern is to call `add_to_queue` multiple times at once, and
126 # deferring to the next reactor tick allows us to batch all of
127 # those up.
128 await self._clock.sleep(0)
129
130 next_values = self._next_values.pop(key, [])
131 if not next_values:
132 # We've exhausted the queue.
133 break
134
135 try:
136 values = [value for value, _ in next_values]
137 results = await self._process_batch_callback(values)
138
139 for _, deferred in next_values:
140 with PreserveLoggingContext():
141 deferred.callback(results)
142
143 except Exception as e:
144 for _, deferred in next_values:
145 if deferred.called:
146 continue
147
148 with PreserveLoggingContext():
149 deferred.errback(e)
150
151 finally:
152 self._processing_keys.discard(key)
6969 self,
7070 name: str,
7171 max_entries: int = 1000,
72 keylen: int = 1,
7372 tree: bool = False,
7473 iterable: bool = False,
7574 apply_cache_factor_from_config: bool = True,
10099 # a Deferred.
101100 self.cache = LruCache(
102101 max_size=max_entries,
103 keylen=keylen,
104102 cache_name=name,
105103 cache_type=cache_type,
106104 size_callback=(lambda d: len(d) or 1) if iterable else None,
269269 cache = DeferredCache(
270270 name=self.orig.__name__,
271271 max_entries=self.max_entries,
272 keylen=self.num_args,
273272 tree=self.tree,
274273 iterable=self.iterable,
275274 ) # type: DeferredCache[CacheKey, Any]
321320 class DeferredCacheListDescriptor(_CacheDescriptorBase):
322321 """Wraps an existing cache to support bulk fetching of keys.
323322
324 Given a list of keys it looks in the cache to find any hits, then passes
325 the list of missing keys to the wrapped function.
323 Given an iterable of keys it looks in the cache to find any hits, then passes
324 the tuple of missing keys to the wrapped function.
326325
327326 Once wrapped, the function returns a Deferred which resolves to the list
328327 of results.
436435 return f
437436
438437 args_to_call = dict(arg_dict)
439 args_to_call[self.list_name] = list(missing)
438 # copy the missing set before sending it to the callee, to guard against
439 # modification.
440 args_to_call[self.list_name] = tuple(missing)
440441
441442 cached_defers.append(
442443 defer.maybeDeferred(
521522
522523 Used to do batch lookups for an already created cache. A single argument
523524 is specified as a list that is iterated through to lookup keys in the
524 original cache. A new list consisting of the keys that weren't in the cache
525 get passed to the original function, the result of which is stored in the
525 original cache. A new tuple consisting of the (deduplicated) keys that weren't in
526 the cache gets passed to the original function, the result of which is stored in the
526527 cache.
527528
528529 Args:
529530 cached_method_name: The name of the single-item lookup method.
530531 This is only used to find the cache to use.
531 list_name: The name of the argument that is the list to use to
532 list_name: The name of the argument that is the iterable to use to
532533 do batch lookups in the cache.
533534 num_args: Number of arguments to use as the key in the cache
534535 (including list_name). Defaults to all named parameters.
3333 from synapse.config import cache as cache_config
3434 from synapse.util import caches
3535 from synapse.util.caches import CacheMetric, register_cache
36 from synapse.util.caches.treecache import TreeCache
36 from synapse.util.caches.treecache import TreeCache, iterate_tree_cache_entry
3737
3838 try:
3939 from pympler.asizeof import Asizer
159159 self,
160160 max_size: int,
161161 cache_name: Optional[str] = None,
162 keylen: int = 1,
163162 cache_type: Type[Union[dict, TreeCache]] = dict,
164163 size_callback: Optional[Callable] = None,
165164 metrics_collection_callback: Optional[Callable[[], None]] = None,
171170
172171 cache_name: The name of this cache, for the prometheus metrics. If unset,
173172 no metrics will be reported on this cache.
174
175 keylen: The length of the tuple used as the cache key. Ignored unless
176 cache_type is `TreeCache`.
177173
178174 cache_type (type):
179175 type of underlying cache to be used. Typically one of dict
402398 popped = cache.pop(key)
403399 if popped is None:
404400 return
405 for leaf in enumerate_leaves(popped, keylen - len(cast(tuple, key))):
401 # for each deleted node, we now need to remove it from the linked list
402 # and run its callbacks.
403 for leaf in iterate_tree_cache_entry(popped):
406404 delete_node(leaf)
407405
408406 @synchronized
0 from typing import Dict
0 # Copyright 2016-2021 The Matrix.org Foundation C.I.C.
1 #
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
113
214 SENTINEL = object()
15
16
17 class TreeCacheNode(dict):
18 """The type of nodes in our tree.
19
20 Has its own type so we can distinguish it from real dicts that are stored at the
21 leaves.
22 """
23
24 pass
325
426
527 class TreeCache:
729 Tree-based backing store for LruCache. Allows subtrees of data to be deleted
830 efficiently.
931 Keys must be tuples.
32
33 The data structure is a chain of TreeCacheNodes:
34 root = {key_1: {key_2: _value}}
1035 """
1136
1237 def __init__(self):
1338 self.size = 0
14 self.root = {} # type: Dict
39 self.root = TreeCacheNode()
1540
1641 def __setitem__(self, key, value):
1742 return self.set(key, value)
2045 return self.get(key, SENTINEL) is not SENTINEL
2146
2247 def set(self, key, value):
48 if isinstance(value, TreeCacheNode):
49 # this would mean we couldn't tell where our tree ended and the value
50 # started.
51 raise ValueError("Cannot store TreeCacheNodes in a TreeCache")
52
2353 node = self.root
2454 for k in key[:-1]:
25 node = node.setdefault(k, {})
26 node[key[-1]] = _Entry(value)
55 next_node = node.get(k, SENTINEL)
56 if next_node is SENTINEL:
57 next_node = node[k] = TreeCacheNode()
58 elif not isinstance(next_node, TreeCacheNode):
59 # this suggests that the caller is not being consistent with its key
60 # length.
61 raise ValueError("value conflicts with an existing subtree")
62 node = next_node
63
64 node[key[-1]] = value
2765 self.size += 1
2866
2967 def get(self, key, default=None):
3270 node = node.get(k, None)
3371 if node is None:
3472 return default
35 return node.get(key[-1], _Entry(default)).value
73 return node.get(key[-1], default)
3674
3775 def clear(self):
3876 self.size = 0
39 self.root = {}
77 self.root = TreeCacheNode()
4078
4179 def pop(self, key, default=None):
80 """Remove the given key, or subkey, from the cache
81
82 Args:
83 key: key or subkey to remove.
84 default: value to return if key is not found
85
86 Returns:
87 If the key is not found, 'default'. If the key is complete, the removed
88 value. If the key is partial, the TreeCacheNode corresponding to the part
89 of the tree that was removed.
90 """
91 # a list of the nodes we have touched on the way down the tree
4292 nodes = []
4393
4494 node = self.root
4595 for k in key[:-1]:
4696 node = node.get(k, None)
47 nodes.append(node) # don't add the root node
4897 if node is None:
4998 return default
99 if not isinstance(node, TreeCacheNode):
100 # we've gone off the end of the tree
101 raise ValueError("pop() key too long")
102 nodes.append(node) # don't add the root node
50103 popped = node.pop(key[-1], SENTINEL)
51104 if popped is SENTINEL:
52105 return default
53106
107 # working back up the tree, clear out any nodes that are now empty
54108 node_and_keys = list(zip(nodes, key))
55109 node_and_keys.reverse()
56110 node_and_keys.append((self.root, None))
60114
61115 if n:
62116 break
117 # found an empty node: remove it from its parent, and loop.
63118 node_and_keys[i + 1][0].pop(k)
64119
65 popped, cnt = _strip_and_count_entires(popped)
120 cnt = sum(1 for _ in iterate_tree_cache_entry(popped))
66121 self.size -= cnt
67122 return popped
68123
69124 def values(self):
70 return list(iterate_tree_cache_entry(self.root))
125 return iterate_tree_cache_entry(self.root)
71126
72127 def __len__(self):
73128 return self.size
77132 """Helper function to iterate over the leaves of a tree, i.e. a dict of that
78133 can contain dicts.
79134 """
80 if isinstance(d, dict):
135 if isinstance(d, TreeCacheNode):
81136 for value_d in d.values():
82137 for value in iterate_tree_cache_entry(value_d):
83138 yield value
84139 else:
85 if isinstance(d, _Entry):
86 yield d.value
87 else:
88 yield d
89
90
91 class _Entry:
92 __slots__ = ["value"]
93
94 def __init__(self, value):
95 self.value = value
96
97
98 def _strip_and_count_entires(d):
99 """Takes an _Entry or dict with leaves of _Entry's, and either returns the
100 value or a dictionary with _Entry's replaced by their values.
101
102 Also returns the count of _Entry's
103 """
104 if isinstance(d, dict):
105 cnt = 0
106 for key, value in d.items():
107 v, n = _strip_and_count_entires(value)
108 d[key] = v
109 cnt += n
110 return d, cnt
111 else:
112 return d.value, 1
140 yield d
1616 import unpaddedbase64
1717
1818
19 def sha256_and_url_safe_base64(input_text):
19 def sha256_and_url_safe_base64(input_text: str) -> str:
2020 """SHA256 hash an input string, encode the digest as url-safe base64, and
2121 return
2222
23 :param input_text: string to hash
24 :type input_text: str
23 Args:
24 input_text: string to hash
2525
26 :returns a sha256 hashed and url-safe base64 encoded digest
27 :rtype: str
26 returns:
27 A sha256 hashed and url-safe base64 encoded digest
2828 """
2929 digest = hashlib.sha256(input_text.encode()).digest()
3030 return unpaddedbase64.encode_base64(digest, urlsafe=True)
2929 T = TypeVar("T")
3030
3131
32 def batch_iter(iterable: Iterable[T], size: int) -> Iterator[Tuple[T]]:
32 def batch_iter(iterable: Iterable[T], size: int) -> Iterator[Tuple[T, ...]]:
3333 """batch an iterable up into tuples with a maximum size
3434
3535 Args:
36 iterable (iterable): the iterable to slice
37 size (int): the maximum batch size
36 iterable: the iterable to slice
37 size: the maximum batch size
3838
3939 Returns:
4040 an iterator over the chunks
4545 return iter(lambda: tuple(islice(sourceiter, size)), ())
4646
4747
48 ISeq = TypeVar("ISeq", bound=Sequence, covariant=True)
49
50
51 def chunk_seq(iseq: ISeq, maxlen: int) -> Iterable[ISeq]:
48 def chunk_seq(iseq: Sequence[T], maxlen: int) -> Iterable[Sequence[T]]:
5249 """Split the given sequence into chunks of the given size
5350
5451 The last chunk may be shorter than the given size.
1414 import importlib
1515 import importlib.util
1616 import itertools
17 from types import ModuleType
1718 from typing import Any, Iterable, Tuple, Type
1819
1920 import jsonschema
4344
4445 # We need to import the module, and then pick the class out of
4546 # that, so we split based on the last dot.
46 module, clz = modulename.rsplit(".", 1)
47 module = importlib.import_module(module)
47 module_name, clz = modulename.rsplit(".", 1)
48 module = importlib.import_module(module_name)
4849 provider_class = getattr(module, clz)
4950
5051 # Load the module config. If None, pass an empty dictionary instead
6869 return provider_class, provider_config
6970
7071
71 def load_python_module(location: str):
72 def load_python_module(location: str) -> ModuleType:
7273 """Load a python module, and return a reference to its global namespace
7374
7475 Args:
75 location (str): path to the module
76 location: path to the module
7677
7778 Returns:
7879 python module object
1616 from synapse.api.errors import SynapseError
1717
1818
19 def phone_number_to_msisdn(country, number):
19 def phone_number_to_msisdn(country: str, number: str) -> str:
2020 """
2121 Takes an ISO-3166-1 2 letter country code and phone number and
2222 returns an msisdn representing the canonical version of that
2323 phone number.
2424 Args:
25 country (str): ISO-3166-1 2 letter country code
26 number (str): Phone number in a national or international format
25 country: ISO-3166-1 2 letter country code
26 number: Phone number in a national or international format
2727
2828 Returns:
29 (str) The canonical form of the phone number, as an msisdn
29 The canonical form of the phone number, as an msisdn
3030 Raises:
31 SynapseError if the number could not be parsed.
31 SynapseError if the number could not be parsed.
3232 """
3333 try:
3434 phoneNumber = phonenumbers.parse(number, country)
8181 retry_timings = await store.get_destination_retry_timings(destination)
8282
8383 if retry_timings:
84 failure_ts = retry_timings["failure_ts"]
85 retry_last_ts, retry_interval = (
86 retry_timings["retry_last_ts"],
87 retry_timings["retry_interval"],
88 )
84 failure_ts = retry_timings.failure_ts
85 retry_last_ts = retry_timings.retry_last_ts
86 retry_interval = retry_timings.retry_interval
8987
9088 now = int(clock.time_msec())
9189
1212 # See the License for the specific language governing permissions and
1313 # limitations under the License.
1414 import itertools
15 import random
1615 import re
16 import secrets
1717 import string
1818 from collections.abc import Iterable
1919 from typing import Optional, Tuple
3434 #
3535 MXC_REGEX = re.compile("^mxc://([^/]+)/([^/#?]+)$")
3636
37 # random_string and random_string_with_symbols are used for a range of things,
38 # some cryptographically important, some less so. We use SystemRandom to make sure
39 # we get cryptographically-secure randoms.
40 rand = random.SystemRandom()
41
4237
4338 def random_string(length: int) -> str:
44 return "".join(rand.choice(string.ascii_letters) for _ in range(length))
39 """Generate a cryptographically secure string of random letters.
40
41 Drawn from the characters: `a-z` and `A-Z`
42 """
43 return "".join(secrets.choice(string.ascii_letters) for _ in range(length))
4544
4645
4746 def random_string_with_symbols(length: int) -> str:
48 return "".join(rand.choice(_string_with_symbols) for _ in range(length))
47 """Generate a cryptographically secure string of random letters/numbers/symbols.
48
49 Drawn from the characters: `a-z`, `A-Z`, `0-9`, and `.,;:^&*-_+=#~@`
50 """
51 return "".join(secrets.choice(_string_with_symbols) for _ in range(length))
4952
5053
5154 def is_ascii(s: bytes) -> bool:
5255 try:
5356 s.decode("ascii").encode("ascii")
54 except UnicodeDecodeError:
55 return False
56 except UnicodeEncodeError:
57 except UnicodeError:
5758 return False
5859 return True
5960
2323 import subprocess
2424 import sys
2525 import time
26 from typing import Iterable
2627
2728 import yaml
2829
2930 from synapse.config import find_config_files
3031
31 SYNAPSE = [sys.executable, "-m", "synapse.app.homeserver"]
32 MAIN_PROCESS = "synapse.app.homeserver"
3233
3334 GREEN = "\x1b[1;32m"
3435 YELLOW = "\x1b[1;33m"
6768 sys.exit(1)
6869
6970
70 def start(configfile: str, daemonize: bool = True) -> bool:
71 """Attempts to start synapse.
71 def start(pidfile: str, app: str, config_files: Iterable[str], daemonize: bool) -> bool:
72 """Attempts to start a synapse main or worker process.
7273 Args:
73 configfile: path to a yaml synapse config file
74 daemonize: whether to daemonize synapse or keep it attached to the current
75 session
74 pidfile: the pidfile we expect the process to create
75 app: the python module to run
76 config_files: config files to pass to synapse
77 daemonize: if True, will include a --daemonize argument to synapse
7678
7779 Returns:
78 True if the process started successfully
80 True if the process started successfully or was already running
7981 False if there was an error starting the process
80
81 If deamonize is False it will only return once synapse exits.
8282 """
8383
84 write("Starting ...")
85 args = SYNAPSE
86
84 if os.path.exists(pidfile) and pid_running(int(open(pidfile).read())):
85 print(app + " already running")
86 return True
87
88 args = [sys.executable, "-m", app]
89 for c in config_files:
90 args += ["-c", c]
8791 if daemonize:
88 args.extend(["--daemonize", "-c", configfile])
89 else:
90 args.extend(["-c", configfile])
92 args.append("--daemonize")
9193
9294 try:
9395 subprocess.check_call(args)
94 write("started synapse.app.homeserver(%r)" % (configfile,), colour=GREEN)
96 write("started %s(%s)" % (app, ",".join(config_files)), colour=GREEN)
9597 return True
9698 except subprocess.CalledProcessError as e:
9799 write(
98 "error starting (exit code: %d); see above for logs" % e.returncode,
99 colour=RED,
100 )
101 return False
102
103
104 def start_worker(app: str, configfile: str, worker_configfile: str) -> bool:
105 """Attempts to start a synapse worker.
106 Args:
107 app: name of the worker's appservice
108 configfile: path to a yaml synapse config file
109 worker_configfile: path to worker specific yaml synapse file
110
111 Returns:
112 True if the process started successfully
113 False if there was an error starting the process
114 """
115
116 args = [
117 sys.executable,
118 "-m",
119 app,
120 "-c",
121 configfile,
122 "-c",
123 worker_configfile,
124 "--daemonize",
125 ]
126
127 try:
128 subprocess.check_call(args)
129 write("started %s(%r)" % (app, worker_configfile), colour=GREEN)
130 return True
131 except subprocess.CalledProcessError as e:
132 write(
133 "error starting %s(%r) (exit code: %d); see above for logs"
134 % (app, worker_configfile, e.returncode),
100 "error starting %s(%s) (exit code: %d); see above for logs"
101 % (app, ",".join(config_files), e.returncode),
135102 colour=RED,
136103 )
137104 return False
223190
224191 if not os.path.exists(configfile):
225192 write(
226 "No config file found\n"
227 "To generate a config file, run '%s -c %s --generate-config"
228 " --server-name=<server name> --report-stats=<yes/no>'\n"
229 % (" ".join(SYNAPSE), options.configfile),
193 f"Config file {configfile} does not exist.\n"
194 f"To generate a config file, run:\n"
195 f" {sys.executable} -m {MAIN_PROCESS}"
196 f" -c {configfile} --generate-config"
197 f" --server-name=<server name> --report-stats=<yes/no>\n",
230198 stream=sys.stderr,
231199 )
232200 sys.exit(1)
322290 has_stopped = False
323291
324292 if start_stop_synapse:
325 if not stop(pidfile, "synapse.app.homeserver"):
293 if not stop(pidfile, MAIN_PROCESS):
326294 has_stopped = False
327295 if not has_stopped and action == "stop":
328296 sys.exit(1)
345313 if action == "start" or action == "restart":
346314 error = False
347315 if start_stop_synapse:
348 # Check if synapse is already running
349 if os.path.exists(pidfile) and pid_running(int(open(pidfile).read())):
350 abort("synapse.app.homeserver already running")
351
352 if not start(configfile, bool(options.daemonize)):
316 if not start(pidfile, MAIN_PROCESS, (configfile,), options.daemonize):
353317 error = True
354318
355319 for worker in workers:
356320 env = os.environ.copy()
357321
358 # Skip starting a worker if its already running
359 if os.path.exists(worker.pidfile) and pid_running(
360 int(open(worker.pidfile).read())
361 ):
362 print(worker.app + " already running")
363 continue
364
365322 if worker.cache_factor:
366323 os.environ["SYNAPSE_CACHE_FACTOR"] = str(worker.cache_factor)
367324
368325 for cache_name, factor in worker.cache_factors.items():
369326 os.environ["SYNAPSE_CACHE_FACTOR_" + cache_name.upper()] = str(factor)
370327
371 if not start_worker(worker.app, configfile, worker.configfile):
328 if not start(
329 worker.pidfile,
330 worker.app,
331 (configfile, worker.configfile),
332 options.daemonize,
333 ):
372334 error = True
373335
374336 # Reset env back to the original
301301 )
302302
303303 # Check that the expected presence updates were sent
304 expected_users = [
304 # We explicitly compare using sets as we expect that calling
305 # module_api.send_local_online_presence_to will create a presence
306 # update that is a duplicate of the specified user's current presence.
307 # These are sent to clients and will be picked up below, thus we use a
308 # set to deduplicate. We're just interested that non-offline updates were
309 # sent out for each user ID.
310 expected_users = {
305311 self.other_user_id,
306312 self.presence_receiving_user_one_id,
307313 self.presence_receiving_user_two_id,
308 ]
314 }
315 found_users = set()
309316
310317 calls = (
311318 self.hs.get_federation_transport_client().send_transaction.call_args_list
325332 # EDUs can contain multiple presence updates
326333 for presence_update in edu["content"]["push"]:
327334 # Check for presence updates that contain the user IDs we're after
328 expected_users.remove(presence_update["user_id"])
335 found_users.add(presence_update["user_id"])
329336
330337 # Ensure that no offline states are being sent out
331338 self.assertNotEqual(presence_update["presence"], "offline")
332339
333 self.assertEqual(len(expected_users), 0)
340 self.assertEqual(found_users, expected_users)
334341
335342
336343 def send_presence_update(
3131 handle_timeout,
3232 handle_update,
3333 )
34 from synapse.rest import admin
3435 from synapse.rest.client.v1 import room
3536 from synapse.types import UserID, get_domain_from_id
3637
3738 from tests import unittest
3839
3940
40 class PresenceUpdateTestCase(unittest.TestCase):
41 class PresenceUpdateTestCase(unittest.HomeserverTestCase):
42 servlets = [admin.register_servlets]
43
44 def prepare(self, reactor, clock, homeserver):
45 self.store = homeserver.get_datastore()
46
4147 def test_offline_to_online(self):
4248 wheel_timer = Mock()
4349 user_id = "@foo:bar"
291297 any_order=True,
292298 )
293299
300 def test_persisting_presence_updates(self):
301 """Tests that the latest presence state for each user is persisted correctly"""
302 # Create some test users and presence states for them
303 presence_states = []
304 for i in range(5):
305 user_id = self.register_user(f"user_{i}", "password")
306
307 presence_state = UserPresenceState(
308 user_id=user_id,
309 state="online",
310 last_active_ts=1,
311 last_federation_update_ts=1,
312 last_user_sync_ts=1,
313 status_msg="I'm online!",
314 currently_active=True,
315 )
316 presence_states.append(presence_state)
317
318 # Persist these presence updates to the database
319 self.get_success(self.store.update_presence(presence_states))
320
321 # Check that each update is present in the database
322 db_presence_states = self.get_success(
323 self.store.get_all_presence_updates(
324 instance_name="master",
325 last_id=0,
326 current_id=len(presence_states) + 1,
327 limit=len(presence_states),
328 )
329 )
330
331 # Extract presence update user ID and state information into lists of tuples
332 db_presence_states = [(ps[0], ps[1]) for _, ps in db_presence_states[0]]
333 presence_states = [(ps.user_id, ps.state) for ps in presence_states]
334
335 # Compare what we put into the storage with what we got out.
336 # They should be identical.
337 self.assertEqual(presence_states, db_presence_states)
338
294339
295340 class PresenceTimeoutTestCase(unittest.TestCase):
296341 def test_idle_timer(self):
8888 self.event_source = hs.get_event_sources().sources["typing"]
8989
9090 self.datastore = hs.get_datastore()
91 retry_timings_res = {
92 "destination": "",
93 "retry_last_ts": 0,
94 "retry_interval": 0,
95 "failure_ts": None,
96 }
9791 self.datastore.get_destination_retry_timings = Mock(
98 return_value=defer.succeed(retry_timings_res)
92 return_value=defer.succeed(None)
9993 )
10094
10195 self.datastore.get_device_updates_by_remote = Mock(
1212 # limitations under the License.
1313 from unittest.mock import Mock
1414
15 from twisted.internet import defer
16
1517 from synapse.api.constants import EduTypes
1618 from synapse.events import EventBase
1719 from synapse.federation.units import Transaction
2123 from synapse.types import create_requester
2224
2325 from tests.events.test_presence_router import send_presence_update, sync_presence
26 from tests.replication._base import BaseMultiWorkerStreamTestCase
2427 from tests.test_utils.event_injection import inject_member_event
25 from tests.unittest import FederatingHomeserverTestCase, override_config
26
27
28 class ModuleApiTestCase(FederatingHomeserverTestCase):
28 from tests.unittest import HomeserverTestCase, override_config
29 from tests.utils import USE_POSTGRES_FOR_TESTS
30
31
32 class ModuleApiTestCase(HomeserverTestCase):
2933 servlets = [
3034 admin.register_servlets,
3135 login.register_servlets,
216220 )
217221 self.assertFalse(is_in_public_rooms)
218222
219 # The ability to send federation is required by send_local_online_presence_to.
220 @override_config({"send_federation": True})
221223 def test_send_local_online_presence_to(self):
222 """Tests that send_local_presence_to_users sends local online presence to local users."""
223 # Create a user who will send presence updates
224 self.presence_receiver_id = self.register_user("presence_receiver", "monkey")
225 self.presence_receiver_tok = self.login("presence_receiver", "monkey")
226
227 # And another user that will send presence updates out
228 self.presence_sender_id = self.register_user("presence_sender", "monkey")
229 self.presence_sender_tok = self.login("presence_sender", "monkey")
230
231 # Put them in a room together so they will receive each other's presence updates
232 room_id = self.helper.create_room_as(
233 self.presence_receiver_id,
234 tok=self.presence_receiver_tok,
235 )
236 self.helper.join(room_id, self.presence_sender_id, tok=self.presence_sender_tok)
237
238 # Presence sender comes online
239 send_presence_update(
240 self,
241 self.presence_sender_id,
242 self.presence_sender_tok,
243 "online",
244 "I'm online!",
245 )
246
247 # Presence receiver should have received it
248 presence_updates, sync_token = sync_presence(self, self.presence_receiver_id)
249 self.assertEqual(len(presence_updates), 1)
250
251 presence_update = presence_updates[0] # type: UserPresenceState
252 self.assertEqual(presence_update.user_id, self.presence_sender_id)
253 self.assertEqual(presence_update.state, "online")
254
255 # Syncing again should result in no presence updates
256 presence_updates, sync_token = sync_presence(
257 self, self.presence_receiver_id, sync_token
258 )
259 self.assertEqual(len(presence_updates), 0)
260
261 # Trigger sending local online presence
262 self.get_success(
263 self.module_api.send_local_online_presence_to(
264 [
265 self.presence_receiver_id,
266 ]
267 )
268 )
269
270 # Presence receiver should have received online presence again
271 presence_updates, sync_token = sync_presence(
272 self, self.presence_receiver_id, sync_token
273 )
274 self.assertEqual(len(presence_updates), 1)
275
276 presence_update = presence_updates[0] # type: UserPresenceState
277 self.assertEqual(presence_update.user_id, self.presence_sender_id)
278 self.assertEqual(presence_update.state, "online")
279
280 # Presence sender goes offline
281 send_presence_update(
282 self,
283 self.presence_sender_id,
284 self.presence_sender_tok,
285 "offline",
286 "I slink back into the darkness.",
287 )
288
289 # Trigger sending local online presence
290 self.get_success(
291 self.module_api.send_local_online_presence_to(
292 [
293 self.presence_receiver_id,
294 ]
295 )
296 )
297
298 # Presence receiver should *not* have received offline state
299 presence_updates, sync_token = sync_presence(
300 self, self.presence_receiver_id, sync_token
301 )
302 self.assertEqual(len(presence_updates), 0)
224 # Test sending local online presence to users from the main process
225 _test_sending_local_online_presence_to_local_user(self, test_with_workers=False)
303226
304227 @override_config({"send_federation": True})
305228 def test_send_local_online_presence_to_federation(self):
306229 """Tests that send_local_presence_to_users sends local online presence to remote users."""
307230 # Create a user who will send presence updates
308 self.presence_sender_id = self.register_user("presence_sender", "monkey")
309 self.presence_sender_tok = self.login("presence_sender", "monkey")
231 self.presence_sender_id = self.register_user("presence_sender1", "monkey")
232 self.presence_sender_tok = self.login("presence_sender1", "monkey")
310233
311234 # And a room they're a part of
312235 room_id = self.helper.create_room_as(
373296 found_update = True
374297
375298 self.assertTrue(found_update)
299
300
301 class ModuleApiWorkerTestCase(BaseMultiWorkerStreamTestCase):
302 """For testing ModuleApi functionality in a multi-worker setup"""
303
304 # Testing stream ID replication from the main to worker processes requires postgres
305 # (due to needing `MultiWriterIdGenerator`).
306 if not USE_POSTGRES_FOR_TESTS:
307 skip = "Requires Postgres"
308
309 servlets = [
310 admin.register_servlets,
311 login.register_servlets,
312 room.register_servlets,
313 presence.register_servlets,
314 ]
315
316 def default_config(self):
317 conf = super().default_config()
318 conf["redis"] = {"enabled": "true"}
319 conf["stream_writers"] = {"presence": ["presence_writer"]}
320 conf["instance_map"] = {
321 "presence_writer": {"host": "testserv", "port": 1001},
322 }
323 return conf
324
325 def prepare(self, reactor, clock, homeserver):
326 self.module_api = homeserver.get_module_api()
327 self.sync_handler = homeserver.get_sync_handler()
328
329 def test_send_local_online_presence_to_workers(self):
330 # Test sending local online presence to users from a worker process
331 _test_sending_local_online_presence_to_local_user(self, test_with_workers=True)
332
333
334 def _test_sending_local_online_presence_to_local_user(
335 test_case: HomeserverTestCase, test_with_workers: bool = False
336 ):
337 """Tests that send_local_presence_to_users sends local online presence to local users.
338
339 This simultaneously tests two different usecases:
340 * Testing that this method works when either called from a worker or the main process.
341 - We test this by calling this method from both a TestCase that runs in monolith mode, and one that
342 runs with a main and generic_worker.
343 * Testing that multiple devices syncing simultaneously will all receive a snapshot of local,
344 online presence - but only once per device.
345
346 Args:
347 test_with_workers: If True, this method will call ModuleApi.send_local_online_presence_to on a
348 worker process. The test users will still sync with the main process. The purpose of testing
349 with a worker is to check whether a Synapse module running on a worker can inform other workers/
350 the main process that they should include additional presence when a user next syncs.
351 """
352 if test_with_workers:
353 # Create a worker process to make module_api calls against
354 worker_hs = test_case.make_worker_hs(
355 "synapse.app.generic_worker", {"worker_name": "presence_writer"}
356 )
357
358 # Create a user who will send presence updates
359 test_case.presence_receiver_id = test_case.register_user(
360 "presence_receiver1", "monkey"
361 )
362 test_case.presence_receiver_tok = test_case.login("presence_receiver1", "monkey")
363
364 # And another user that will send presence updates out
365 test_case.presence_sender_id = test_case.register_user("presence_sender2", "monkey")
366 test_case.presence_sender_tok = test_case.login("presence_sender2", "monkey")
367
368 # Put them in a room together so they will receive each other's presence updates
369 room_id = test_case.helper.create_room_as(
370 test_case.presence_receiver_id,
371 tok=test_case.presence_receiver_tok,
372 )
373 test_case.helper.join(
374 room_id, test_case.presence_sender_id, tok=test_case.presence_sender_tok
375 )
376
377 # Presence sender comes online
378 send_presence_update(
379 test_case,
380 test_case.presence_sender_id,
381 test_case.presence_sender_tok,
382 "online",
383 "I'm online!",
384 )
385
386 # Presence receiver should have received it
387 presence_updates, sync_token = sync_presence(
388 test_case, test_case.presence_receiver_id
389 )
390 test_case.assertEqual(len(presence_updates), 1)
391
392 presence_update = presence_updates[0] # type: UserPresenceState
393 test_case.assertEqual(presence_update.user_id, test_case.presence_sender_id)
394 test_case.assertEqual(presence_update.state, "online")
395
396 if test_with_workers:
397 # Replicate the current sync presence token from the main process to the worker process.
398 # We need to do this so that the worker process knows the current presence stream ID to
399 # insert into the database when we call ModuleApi.send_local_online_presence_to.
400 test_case.replicate()
401
402 # Syncing again should result in no presence updates
403 presence_updates, sync_token = sync_presence(
404 test_case, test_case.presence_receiver_id, sync_token
405 )
406 test_case.assertEqual(len(presence_updates), 0)
407
408 # We do an (initial) sync with a second "device" now, getting a new sync token.
409 # We'll use this in a moment.
410 _, sync_token_second_device = sync_presence(
411 test_case, test_case.presence_receiver_id
412 )
413
414 # Determine on which process (main or worker) to call ModuleApi.send_local_online_presence_to on
415 if test_with_workers:
416 module_api_to_use = worker_hs.get_module_api()
417 else:
418 module_api_to_use = test_case.module_api
419
420 # Trigger sending local online presence. We expect this information
421 # to be saved to the database where all processes can access it.
422 # Note that we're syncing via the master.
423 d = module_api_to_use.send_local_online_presence_to(
424 [
425 test_case.presence_receiver_id,
426 ]
427 )
428 d = defer.ensureDeferred(d)
429
430 if test_with_workers:
431 # In order for the required presence_set_state replication request to occur between the
432 # worker and main process, we need to pump the reactor. Otherwise, the coordinator that
433 # reads the request on the main process won't do so, and the request will time out.
434 while not d.called:
435 test_case.reactor.advance(0.1)
436
437 test_case.get_success(d)
438
439 # The presence receiver should have received online presence again.
440 presence_updates, sync_token = sync_presence(
441 test_case, test_case.presence_receiver_id, sync_token
442 )
443 test_case.assertEqual(len(presence_updates), 1)
444
445 presence_update = presence_updates[0] # type: UserPresenceState
446 test_case.assertEqual(presence_update.user_id, test_case.presence_sender_id)
447 test_case.assertEqual(presence_update.state, "online")
448
449 # We attempt to sync with the second sync token we received above - just to check that
450 # multiple syncing devices will each receive the necessary online presence.
451 presence_updates, sync_token_second_device = sync_presence(
452 test_case, test_case.presence_receiver_id, sync_token_second_device
453 )
454 test_case.assertEqual(len(presence_updates), 1)
455
456 presence_update = presence_updates[0] # type: UserPresenceState
457 test_case.assertEqual(presence_update.user_id, test_case.presence_sender_id)
458 test_case.assertEqual(presence_update.state, "online")
459
460 # However, if we now sync with either "device", we won't receive another burst of online presence
461 # until the API is called again sometime in the future
462 presence_updates, sync_token = sync_presence(
463 test_case, test_case.presence_receiver_id, sync_token
464 )
465
466 # Now we check that we don't receive *offline* updates using ModuleApi.send_local_online_presence_to.
467
468 # Presence sender goes offline
469 send_presence_update(
470 test_case,
471 test_case.presence_sender_id,
472 test_case.presence_sender_tok,
473 "offline",
474 "I slink back into the darkness.",
475 )
476
477 # Presence receiver should have received the updated, offline state
478 presence_updates, sync_token = sync_presence(
479 test_case, test_case.presence_receiver_id, sync_token
480 )
481 test_case.assertEqual(len(presence_updates), 1)
482
483 # Now trigger sending local online presence.
484 d = module_api_to_use.send_local_online_presence_to(
485 [
486 test_case.presence_receiver_id,
487 ]
488 )
489 d = defer.ensureDeferred(d)
490
491 if test_with_workers:
492 # In order for the required presence_set_state replication request to occur between the
493 # worker and main process, we need to pump the reactor. Otherwise, the coordinator that
494 # reads the request on the main process won't do so, and the request will time out.
495 while not d.called:
496 test_case.reactor.advance(0.1)
497
498 test_case.get_success(d)
499
500 # Presence receiver should *not* have received offline state
501 presence_updates, sync_token = sync_presence(
502 test_case, test_case.presence_receiver_id, sync_token
503 )
504 test_case.assertEqual(len(presence_updates), 0)
2929 """Checks event persisting sharding works"""
3030
3131 # Event persister sharding requires postgres (due to needing
32 # `MutliWriterIdGenerator`).
32 # `MultiWriterIdGenerator`).
3333 if not USE_POSTGRES_FOR_TESTS:
3434 skip = "Requires Postgres"
3535
1111 # See the License for the specific language governing permissions and
1212 # limitations under the License.
1313
14 from synapse.storage.databases.main.transactions import DestinationRetryTimings
1415 from synapse.util.retryutils import MAX_RETRY_INTERVAL
1516
1617 from tests.unittest import HomeserverTestCase
3536 d = self.store.get_destination_retry_timings("example.com")
3637 r = self.get_success(d)
3738
38 self.assert_dict(
39 {"retry_last_ts": 50, "retry_interval": 100, "failure_ts": 1000}, r
39 self.assertEqual(
40 DestinationRetryTimings(
41 retry_last_ts=50, retry_interval=100, failure_ts=1000
42 ),
43 r,
4044 )
4145
4246 def test_initial_set_transactions(self):
665665 with LoggingContext("c1") as c1:
666666 obj = Cls()
667667 obj.mock.return_value = {10: "fish", 20: "chips"}
668
669 # start the lookup off
668670 d1 = obj.list_fn([10, 20], 2)
669671 self.assertEqual(current_context(), SENTINEL_CONTEXT)
670672 r = yield d1
671673 self.assertEqual(current_context(), c1)
672 obj.mock.assert_called_once_with([10, 20], 2)
674 obj.mock.assert_called_once_with((10, 20), 2)
673675 self.assertEqual(r, {10: "fish", 20: "chips"})
674676 obj.mock.reset_mock()
675677
676678 # a call with different params should call the mock again
677679 obj.mock.return_value = {30: "peas"}
678680 r = yield obj.list_fn([20, 30], 2)
679 obj.mock.assert_called_once_with([30], 2)
681 obj.mock.assert_called_once_with((30,), 2)
680682 self.assertEqual(r, {20: "chips", 30: "peas"})
681683 obj.mock.reset_mock()
682684
691693 obj.mock.assert_not_called()
692694 self.assertEqual(r, {10: "fish", 20: "chips", 30: "peas"})
693695
696 # we should also be able to use a (single-use) iterable, and should
697 # deduplicate the keys
698 obj.mock.reset_mock()
699 obj.mock.return_value = {40: "gravy"}
700 iterable = (x for x in [10, 40, 40])
701 r = yield obj.list_fn(iterable, 2)
702 obj.mock.assert_called_once_with((40,), 2)
703 self.assertEqual(r, {10: "fish", 40: "gravy"})
704
694705 @defer.inlineCallbacks
695706 def test_invalidate(self):
696707 """Make sure that invalidation callbacks are called."""
716727 # cache miss
717728 obj.mock.return_value = {10: "fish", 20: "chips"}
718729 r1 = yield obj.list_fn([10, 20], 2, on_invalidate=invalidate0)
719 obj.mock.assert_called_once_with([10, 20], 2)
730 obj.mock.assert_called_once_with((10, 20), 2)
720731 self.assertEqual(r1, {10: "fish", 20: "chips"})
721732 obj.mock.reset_mock()
722733
0 # Copyright 2021 The Matrix.org Foundation C.I.C.
1 #
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13 from twisted.internet import defer
14
15 from synapse.logging.context import make_deferred_yieldable
16 from synapse.util.batching_queue import BatchingQueue
17
18 from tests.server import get_clock
19 from tests.unittest import TestCase
20
21
22 class BatchingQueueTestCase(TestCase):
23 def setUp(self):
24 self.clock, hs_clock = get_clock()
25
26 self._pending_calls = []
27 self.queue = BatchingQueue("test_queue", hs_clock, self._process_queue)
28
29 async def _process_queue(self, values):
30 d = defer.Deferred()
31 self._pending_calls.append((values, d))
32 return await make_deferred_yieldable(d)
33
34 def test_simple(self):
35 """Tests the basic case of calling `add_to_queue` once and having
36 `_process_queue` return.
37 """
38
39 self.assertFalse(self._pending_calls)
40
41 queue_d = defer.ensureDeferred(self.queue.add_to_queue("foo"))
42
43 # The queue should wait a reactor tick before calling the processing
44 # function.
45 self.assertFalse(self._pending_calls)
46 self.assertFalse(queue_d.called)
47
48 # We should see a call to `_process_queue` after a reactor tick.
49 self.clock.pump([0])
50
51 self.assertEqual(len(self._pending_calls), 1)
52 self.assertEqual(self._pending_calls[0][0], ["foo"])
53 self.assertFalse(queue_d.called)
54
55 # Return value of the `_process_queue` should be propagated back.
56 self._pending_calls.pop()[1].callback("bar")
57
58 self.assertEqual(self.successResultOf(queue_d), "bar")
59
60 def test_batching(self):
61 """Test that multiple calls at the same time get batched up into one
62 call to `_process_queue`.
63 """
64
65 self.assertFalse(self._pending_calls)
66
67 queue_d1 = defer.ensureDeferred(self.queue.add_to_queue("foo1"))
68 queue_d2 = defer.ensureDeferred(self.queue.add_to_queue("foo2"))
69
70 self.clock.pump([0])
71
72 # We should see only *one* call to `_process_queue`
73 self.assertEqual(len(self._pending_calls), 1)
74 self.assertEqual(self._pending_calls[0][0], ["foo1", "foo2"])
75 self.assertFalse(queue_d1.called)
76 self.assertFalse(queue_d2.called)
77
78 # Return value of the `_process_queue` should be propagated back to both.
79 self._pending_calls.pop()[1].callback("bar")
80
81 self.assertEqual(self.successResultOf(queue_d1), "bar")
82 self.assertEqual(self.successResultOf(queue_d2), "bar")
83
84 def test_queuing(self):
85 """Test that we queue up requests while a `_process_queue` is being
86 called.
87 """
88
89 self.assertFalse(self._pending_calls)
90
91 queue_d1 = defer.ensureDeferred(self.queue.add_to_queue("foo1"))
92 self.clock.pump([0])
93
94 queue_d2 = defer.ensureDeferred(self.queue.add_to_queue("foo2"))
95
96 # We should see only *one* call to `_process_queue`
97 self.assertEqual(len(self._pending_calls), 1)
98 self.assertEqual(self._pending_calls[0][0], ["foo1"])
99 self.assertFalse(queue_d1.called)
100 self.assertFalse(queue_d2.called)
101
102 # Return value of the `_process_queue` should be propagated back to the
103 # first.
104 self._pending_calls.pop()[1].callback("bar1")
105
106 self.assertEqual(self.successResultOf(queue_d1), "bar1")
107 self.assertFalse(queue_d2.called)
108
109 # We should now see a second call to `_process_queue`
110 self.clock.pump([0])
111 self.assertEqual(len(self._pending_calls), 1)
112 self.assertEqual(self._pending_calls[0][0], ["foo2"])
113 self.assertFalse(queue_d2.called)
114
115 # Return value of the `_process_queue` should be propagated back to the
116 # second.
117 self._pending_calls.pop()[1].callback("bar2")
118
119 self.assertEqual(self.successResultOf(queue_d2), "bar2")
120
121 def test_different_keys(self):
122 """Test that calls to different keys get processed in parallel."""
123
124 self.assertFalse(self._pending_calls)
125
126 queue_d1 = defer.ensureDeferred(self.queue.add_to_queue("foo1", key=1))
127 self.clock.pump([0])
128 queue_d2 = defer.ensureDeferred(self.queue.add_to_queue("foo2", key=2))
129 self.clock.pump([0])
130
131 # We queue up another item with key=2 to check that we will keep taking
132 # things off the queue.
133 queue_d3 = defer.ensureDeferred(self.queue.add_to_queue("foo3", key=2))
134
135 # We should see two calls to `_process_queue`
136 self.assertEqual(len(self._pending_calls), 2)
137 self.assertEqual(self._pending_calls[0][0], ["foo1"])
138 self.assertEqual(self._pending_calls[1][0], ["foo2"])
139 self.assertFalse(queue_d1.called)
140 self.assertFalse(queue_d2.called)
141 self.assertFalse(queue_d3.called)
142
143 # Return value of the `_process_queue` should be propagated back to the
144 # first.
145 self._pending_calls.pop(0)[1].callback("bar1")
146
147 self.assertEqual(self.successResultOf(queue_d1), "bar1")
148 self.assertFalse(queue_d2.called)
149 self.assertFalse(queue_d3.called)
150
151 # Return value of the `_process_queue` should be propagated back to the
152 # second.
153 self._pending_calls.pop()[1].callback("bar2")
154
155 self.assertEqual(self.successResultOf(queue_d2), "bar2")
156 self.assertFalse(queue_d3.called)
157
158 # We should now see a call `_pending_calls` for `foo3`
159 self.clock.pump([0])
160 self.assertEqual(len(self._pending_calls), 1)
161 self.assertEqual(self._pending_calls[0][0], ["foo3"])
162 self.assertFalse(queue_d3.called)
163
164 # Return value of the `_process_queue` should be propagated back to the
165 # third deferred.
166 self._pending_calls.pop()[1].callback("bar4")
167
168 self.assertEqual(self.successResultOf(queue_d3), "bar4")
1010 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1111 # See the License for the specific language governing permissions and
1212 # limitations under the License.
13 from typing import Dict, List
13 from typing import Dict, Iterable, List, Sequence
1414
1515 from synapse.util.iterutils import chunk_seq, sorted_topologically
1616
4343 )
4444
4545 def test_empty_input(self):
46 parts = chunk_seq([], 5)
46 parts = chunk_seq([], 5) # type: Iterable[Sequence]
4747
4848 self.assertEqual(
4949 list(parts),
5858 self.assertEquals(cache.pop("key"), None)
5959
6060 def test_del_multi(self):
61 cache = LruCache(4, keylen=2, cache_type=TreeCache)
61 cache = LruCache(4, cache_type=TreeCache)
6262 cache[("animal", "cat")] = "mew"
6363 cache[("animal", "dog")] = "woof"
6464 cache[("vehicles", "car")] = "vroom"
164164 m2 = Mock()
165165 m3 = Mock()
166166 m4 = Mock()
167 cache = LruCache(4, keylen=2, cache_type=TreeCache)
167 cache = LruCache(4, cache_type=TreeCache)
168168
169169 cache.set(("a", "1"), "value", callbacks=[m1])
170170 cache.set(("a", "2"), "value", callbacks=[m2])
5050 except AssertionError:
5151 pass
5252
53 self.pump()
54
5355 new_timings = self.get_success(store.get_destination_retry_timings("test_dest"))
54 self.assertEqual(new_timings["failure_ts"], failure_ts)
55 self.assertEqual(new_timings["retry_last_ts"], failure_ts)
56 self.assertEqual(new_timings["retry_interval"], MIN_RETRY_INTERVAL)
56 self.assertEqual(new_timings.failure_ts, failure_ts)
57 self.assertEqual(new_timings.retry_last_ts, failure_ts)
58 self.assertEqual(new_timings.retry_interval, MIN_RETRY_INTERVAL)
5759
5860 # now if we try again we should get a failure
5961 self.get_failure(
7678 except AssertionError:
7779 pass
7880
81 self.pump()
82
7983 new_timings = self.get_success(store.get_destination_retry_timings("test_dest"))
80 self.assertEqual(new_timings["failure_ts"], failure_ts)
81 self.assertEqual(new_timings["retry_last_ts"], retry_ts)
84 self.assertEqual(new_timings.failure_ts, failure_ts)
85 self.assertEqual(new_timings.retry_last_ts, retry_ts)
8286 self.assertGreaterEqual(
83 new_timings["retry_interval"], MIN_RETRY_INTERVAL * RETRY_MULTIPLIER * 0.5
87 new_timings.retry_interval, MIN_RETRY_INTERVAL * RETRY_MULTIPLIER * 0.5
8488 )
8589 self.assertLessEqual(
86 new_timings["retry_interval"], MIN_RETRY_INTERVAL * RETRY_MULTIPLIER * 2.0
90 new_timings.retry_interval, MIN_RETRY_INTERVAL * RETRY_MULTIPLIER * 2.0
8791 )
8892
8993 #
1212 # limitations under the License.
1313
1414
15 from synapse.util.caches.treecache import TreeCache
15 from synapse.util.caches.treecache import TreeCache, iterate_tree_cache_entry
1616
1717 from .. import unittest
1818
6363 cache[("a", "b")] = "AB"
6464 cache[("b", "a")] = "BA"
6565 self.assertEquals(cache.get(("a", "a")), "AA")
66 cache.pop(("a",))
66 popped = cache.pop(("a",))
6767 self.assertEquals(cache.get(("a", "a")), None)
6868 self.assertEquals(cache.get(("a", "b")), None)
6969 self.assertEquals(cache.get(("b", "a")), "BA")
7070 self.assertEquals(len(cache), 1)
71
72 self.assertEquals({"AA", "AB"}, set(iterate_tree_cache_entry(popped)))
7173
7274 def test_clear(self):
7375 cache = TreeCache()
3333 synapse
3434 tests
3535 scripts
36 # annoyingly, black doesn't find these so we have to list them
37 scripts/export_signing_key
38 scripts/generate_config
39 scripts/generate_log_config
40 scripts/hash_password
41 scripts/register_new_matrix_user
42 scripts/synapse_port_db
3643 scripts-dev
44 scripts-dev/build_debian_packages
45 scripts-dev/sign_json
46 scripts-dev/update_database
3747 stubs
3848 contrib
3949 synctl