homeserver.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Copyright 2014-2016 OpenMarket Ltd
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import synapse
  17. import contextlib
  18. import logging
  19. import os
  20. import re
  21. import resource
  22. import subprocess
  23. import sys
  24. import time
  25. from synapse.config._base import ConfigError
  26. from synapse.python_dependencies import (
  27. check_requirements, DEPENDENCY_LINKS
  28. )
  29. from synapse.rest import ClientRestResource
  30. from synapse.storage.engines import create_engine, IncorrectDatabaseSetup
  31. from synapse.storage import are_all_users_on_domain
  32. from synapse.storage.prepare_database import UpgradeDatabaseException, prepare_database
  33. from synapse.server import HomeServer
  34. from twisted.conch.manhole import ColoredManhole
  35. from twisted.conch.insults import insults
  36. from twisted.conch import manhole_ssh
  37. from twisted.cred import checkers, portal
  38. from twisted.internet import reactor, task, defer
  39. from twisted.application import service
  40. from twisted.web.resource import Resource, EncodingResourceWrapper
  41. from twisted.web.static import File
  42. from twisted.web.server import Site, GzipEncoderFactory, Request
  43. from synapse.http.server import RootRedirect
  44. from synapse.rest.media.v0.content_repository import ContentRepoResource
  45. from synapse.rest.media.v1.media_repository import MediaRepositoryResource
  46. from synapse.rest.key.v1.server_key_resource import LocalKey
  47. from synapse.rest.key.v2 import KeyApiV2Resource
  48. from synapse.api.urls import (
  49. FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX,
  50. SERVER_KEY_PREFIX, LEGACY_MEDIA_PREFIX, MEDIA_PREFIX, STATIC_PREFIX,
  51. SERVER_KEY_V2_PREFIX,
  52. )
  53. from synapse.config.homeserver import HomeServerConfig
  54. from synapse.crypto import context_factory
  55. from synapse.util.logcontext import LoggingContext
  56. from synapse.metrics.resource import MetricsResource, METRICS_PREFIX
  57. from synapse.replication.resource import ReplicationResource, REPLICATION_PREFIX
  58. from synapse.federation.transport.server import TransportLayerServer
  59. from synapse import events
  60. from daemonize import Daemonize
  61. logger = logging.getLogger("synapse.app.homeserver")
  62. ACCESS_TOKEN_RE = re.compile(r'(\?.*access(_|%5[Ff])token=)[^&]*(.*)$')
  63. def gz_wrap(r):
  64. return EncodingResourceWrapper(r, [GzipEncoderFactory()])
  65. def build_resource_for_web_client(hs):
  66. webclient_path = hs.get_config().web_client_location
  67. if not webclient_path:
  68. try:
  69. import syweb
  70. except ImportError:
  71. quit_with_error(
  72. "Could not find a webclient.\n\n"
  73. "Please either install the matrix-angular-sdk or configure\n"
  74. "the location of the source to serve via the configuration\n"
  75. "option `web_client_location`\n\n"
  76. "To install the `matrix-angular-sdk` via pip, run:\n\n"
  77. " pip install '%(dep)s'\n"
  78. "\n"
  79. "You can also disable hosting of the webclient via the\n"
  80. "configuration option `web_client`\n"
  81. % {"dep": DEPENDENCY_LINKS["matrix-angular-sdk"]}
  82. )
  83. syweb_path = os.path.dirname(syweb.__file__)
  84. webclient_path = os.path.join(syweb_path, "webclient")
  85. # GZip is disabled here due to
  86. # https://twistedmatrix.com/trac/ticket/7678
  87. # (It can stay enabled for the API resources: they call
  88. # write() with the whole body and then finish() straight
  89. # after and so do not trigger the bug.
  90. # GzipFile was removed in commit 184ba09
  91. # return GzipFile(webclient_path) # TODO configurable?
  92. return File(webclient_path) # TODO configurable?
  93. class SynapseHomeServer(HomeServer):
  94. def _listener_http(self, config, listener_config):
  95. port = listener_config["port"]
  96. bind_address = listener_config.get("bind_address", "")
  97. tls = listener_config.get("tls", False)
  98. site_tag = listener_config.get("tag", port)
  99. if tls and config.no_tls:
  100. return
  101. resources = {}
  102. for res in listener_config["resources"]:
  103. for name in res["names"]:
  104. if name == "client":
  105. client_resource = ClientRestResource(self)
  106. if res["compress"]:
  107. client_resource = gz_wrap(client_resource)
  108. resources.update({
  109. "/_matrix/client/api/v1": client_resource,
  110. "/_matrix/client/r0": client_resource,
  111. "/_matrix/client/unstable": client_resource,
  112. "/_matrix/client/v2_alpha": client_resource,
  113. "/_matrix/client/versions": client_resource,
  114. })
  115. if name == "federation":
  116. resources.update({
  117. FEDERATION_PREFIX: TransportLayerServer(self),
  118. })
  119. if name in ["static", "client"]:
  120. resources.update({
  121. STATIC_PREFIX: File(
  122. os.path.join(os.path.dirname(synapse.__file__), "static")
  123. ),
  124. })
  125. if name in ["media", "federation", "client"]:
  126. media_repo = MediaRepositoryResource(self)
  127. resources.update({
  128. MEDIA_PREFIX: media_repo,
  129. LEGACY_MEDIA_PREFIX: media_repo,
  130. CONTENT_REPO_PREFIX: ContentRepoResource(
  131. self, self.config.uploads_path, self.auth, self.content_addr
  132. ),
  133. })
  134. if name in ["keys", "federation"]:
  135. resources.update({
  136. SERVER_KEY_PREFIX: LocalKey(self),
  137. SERVER_KEY_V2_PREFIX: KeyApiV2Resource(self),
  138. })
  139. if name == "webclient":
  140. resources[WEB_CLIENT_PREFIX] = build_resource_for_web_client(self)
  141. if name == "metrics" and self.get_config().enable_metrics:
  142. resources[METRICS_PREFIX] = MetricsResource(self)
  143. if name == "replication":
  144. resources[REPLICATION_PREFIX] = ReplicationResource(self)
  145. root_resource = create_resource_tree(resources)
  146. if tls:
  147. reactor.listenSSL(
  148. port,
  149. SynapseSite(
  150. "synapse.access.https.%s" % (site_tag,),
  151. site_tag,
  152. listener_config,
  153. root_resource,
  154. ),
  155. self.tls_server_context_factory,
  156. interface=bind_address
  157. )
  158. else:
  159. reactor.listenTCP(
  160. port,
  161. SynapseSite(
  162. "synapse.access.http.%s" % (site_tag,),
  163. site_tag,
  164. listener_config,
  165. root_resource,
  166. ),
  167. interface=bind_address
  168. )
  169. logger.info("Synapse now listening on port %d", port)
  170. def start_listening(self):
  171. config = self.get_config()
  172. for listener in config.listeners:
  173. if listener["type"] == "http":
  174. self._listener_http(config, listener)
  175. elif listener["type"] == "manhole":
  176. checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(
  177. matrix="rabbithole"
  178. )
  179. rlm = manhole_ssh.TerminalRealm()
  180. rlm.chainedProtocolFactory = lambda: insults.ServerProtocol(
  181. ColoredManhole,
  182. {
  183. "__name__": "__console__",
  184. "hs": self,
  185. }
  186. )
  187. f = manhole_ssh.ConchFactory(portal.Portal(rlm, [checker]))
  188. reactor.listenTCP(
  189. listener["port"],
  190. f,
  191. interface=listener.get("bind_address", '127.0.0.1')
  192. )
  193. else:
  194. logger.warn("Unrecognized listener type: %s", listener["type"])
  195. def run_startup_checks(self, db_conn, database_engine):
  196. all_users_native = are_all_users_on_domain(
  197. db_conn.cursor(), database_engine, self.hostname
  198. )
  199. if not all_users_native:
  200. quit_with_error(
  201. "Found users in database not native to %s!\n"
  202. "You cannot changed a synapse server_name after it's been configured"
  203. % (self.hostname,)
  204. )
  205. try:
  206. database_engine.check_database(db_conn.cursor())
  207. except IncorrectDatabaseSetup as e:
  208. quit_with_error(e.message)
  209. def get_db_conn(self, run_new_connection=True):
  210. # Any param beginning with cp_ is a parameter for adbapi, and should
  211. # not be passed to the database engine.
  212. db_params = {
  213. k: v for k, v in self.db_config.get("args", {}).items()
  214. if not k.startswith("cp_")
  215. }
  216. db_conn = self.database_engine.module.connect(**db_params)
  217. if run_new_connection:
  218. self.database_engine.on_new_connection(db_conn)
  219. return db_conn
  220. def quit_with_error(error_string):
  221. message_lines = error_string.split("\n")
  222. line_length = max([len(l) for l in message_lines if len(l) < 80]) + 2
  223. sys.stderr.write("*" * line_length + '\n')
  224. for line in message_lines:
  225. sys.stderr.write(" %s\n" % (line.rstrip(),))
  226. sys.stderr.write("*" * line_length + '\n')
  227. sys.exit(1)
  228. def get_version_string():
  229. try:
  230. null = open(os.devnull, 'w')
  231. cwd = os.path.dirname(os.path.abspath(__file__))
  232. try:
  233. git_branch = subprocess.check_output(
  234. ['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
  235. stderr=null,
  236. cwd=cwd,
  237. ).strip()
  238. git_branch = "b=" + git_branch
  239. except subprocess.CalledProcessError:
  240. git_branch = ""
  241. try:
  242. git_tag = subprocess.check_output(
  243. ['git', 'describe', '--exact-match'],
  244. stderr=null,
  245. cwd=cwd,
  246. ).strip()
  247. git_tag = "t=" + git_tag
  248. except subprocess.CalledProcessError:
  249. git_tag = ""
  250. try:
  251. git_commit = subprocess.check_output(
  252. ['git', 'rev-parse', '--short', 'HEAD'],
  253. stderr=null,
  254. cwd=cwd,
  255. ).strip()
  256. except subprocess.CalledProcessError:
  257. git_commit = ""
  258. try:
  259. dirty_string = "-this_is_a_dirty_checkout"
  260. is_dirty = subprocess.check_output(
  261. ['git', 'describe', '--dirty=' + dirty_string],
  262. stderr=null,
  263. cwd=cwd,
  264. ).strip().endswith(dirty_string)
  265. git_dirty = "dirty" if is_dirty else ""
  266. except subprocess.CalledProcessError:
  267. git_dirty = ""
  268. if git_branch or git_tag or git_commit or git_dirty:
  269. git_version = ",".join(
  270. s for s in
  271. (git_branch, git_tag, git_commit, git_dirty,)
  272. if s
  273. )
  274. return (
  275. "Synapse/%s (%s)" % (
  276. synapse.__version__, git_version,
  277. )
  278. ).encode("ascii")
  279. except Exception as e:
  280. logger.info("Failed to check for git repository: %s", e)
  281. return ("Synapse/%s" % (synapse.__version__,)).encode("ascii")
  282. def change_resource_limit(soft_file_no):
  283. try:
  284. soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
  285. if not soft_file_no:
  286. soft_file_no = hard
  287. resource.setrlimit(resource.RLIMIT_NOFILE, (soft_file_no, hard))
  288. logger.info("Set file limit to: %d", soft_file_no)
  289. resource.setrlimit(
  290. resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)
  291. )
  292. except (ValueError, resource.error) as e:
  293. logger.warn("Failed to set file or core limit: %s", e)
  294. def setup(config_options):
  295. """
  296. Args:
  297. config_options_options: The options passed to Synapse. Usually
  298. `sys.argv[1:]`.
  299. Returns:
  300. HomeServer
  301. """
  302. try:
  303. config = HomeServerConfig.load_config(
  304. "Synapse Homeserver",
  305. config_options,
  306. generate_section="Homeserver"
  307. )
  308. except ConfigError as e:
  309. sys.stderr.write("\n" + e.message + "\n")
  310. sys.exit(1)
  311. if not config:
  312. # If a config isn't returned, and an exception isn't raised, we're just
  313. # generating config files and shouldn't try to continue.
  314. sys.exit(0)
  315. config.setup_logging()
  316. # check any extra requirements we have now we have a config
  317. check_requirements(config)
  318. version_string = get_version_string()
  319. logger.info("Server hostname: %s", config.server_name)
  320. logger.info("Server version: %s", version_string)
  321. events.USE_FROZEN_DICTS = config.use_frozen_dicts
  322. tls_server_context_factory = context_factory.ServerContextFactory(config)
  323. database_engine = create_engine(config.database_config)
  324. config.database_config["args"]["cp_openfun"] = database_engine.on_new_connection
  325. hs = SynapseHomeServer(
  326. config.server_name,
  327. db_config=config.database_config,
  328. tls_server_context_factory=tls_server_context_factory,
  329. config=config,
  330. content_addr=config.content_addr,
  331. version_string=version_string,
  332. database_engine=database_engine,
  333. )
  334. logger.info("Preparing database: %s...", config.database_config['name'])
  335. try:
  336. db_conn = hs.get_db_conn(run_new_connection=False)
  337. prepare_database(db_conn, database_engine, config=config)
  338. database_engine.on_new_connection(db_conn)
  339. hs.run_startup_checks(db_conn, database_engine)
  340. db_conn.commit()
  341. except UpgradeDatabaseException:
  342. sys.stderr.write(
  343. "\nFailed to upgrade database.\n"
  344. "Have you checked for version specific instructions in"
  345. " UPGRADES.rst?\n"
  346. )
  347. sys.exit(1)
  348. logger.info("Database prepared in %s.", config.database_config['name'])
  349. hs.setup()
  350. hs.start_listening()
  351. def start():
  352. hs.get_pusherpool().start()
  353. hs.get_state_handler().start_caching()
  354. hs.get_datastore().start_profiling()
  355. hs.get_datastore().start_doing_background_updates()
  356. hs.get_replication_layer().start_get_pdu_cache()
  357. reactor.callWhenRunning(start)
  358. return hs
  359. class SynapseService(service.Service):
  360. """A twisted Service class that will start synapse. Used to run synapse
  361. via twistd and a .tac.
  362. """
  363. def __init__(self, config):
  364. self.config = config
  365. def startService(self):
  366. hs = setup(self.config)
  367. change_resource_limit(hs.config.soft_file_limit)
  368. def stopService(self):
  369. return self._port.stopListening()
  370. class SynapseRequest(Request):
  371. def __init__(self, site, *args, **kw):
  372. Request.__init__(self, *args, **kw)
  373. self.site = site
  374. self.authenticated_entity = None
  375. self.start_time = 0
  376. def __repr__(self):
  377. # We overwrite this so that we don't log ``access_token``
  378. return '<%s at 0x%x method=%s uri=%s clientproto=%s site=%s>' % (
  379. self.__class__.__name__,
  380. id(self),
  381. self.method,
  382. self.get_redacted_uri(),
  383. self.clientproto,
  384. self.site.site_tag,
  385. )
  386. def get_redacted_uri(self):
  387. return ACCESS_TOKEN_RE.sub(
  388. r'\1<redacted>\3',
  389. self.uri
  390. )
  391. def get_user_agent(self):
  392. return self.requestHeaders.getRawHeaders("User-Agent", [None])[-1]
  393. def started_processing(self):
  394. self.site.access_logger.info(
  395. "%s - %s - Received request: %s %s",
  396. self.getClientIP(),
  397. self.site.site_tag,
  398. self.method,
  399. self.get_redacted_uri()
  400. )
  401. self.start_time = int(time.time() * 1000)
  402. def finished_processing(self):
  403. try:
  404. context = LoggingContext.current_context()
  405. ru_utime, ru_stime = context.get_resource_usage()
  406. db_txn_count = context.db_txn_count
  407. db_txn_duration = context.db_txn_duration
  408. except:
  409. ru_utime, ru_stime = (0, 0)
  410. db_txn_count, db_txn_duration = (0, 0)
  411. self.site.access_logger.info(
  412. "%s - %s - {%s}"
  413. " Processed request: %dms (%dms, %dms) (%dms/%d)"
  414. " %sB %s \"%s %s %s\" \"%s\"",
  415. self.getClientIP(),
  416. self.site.site_tag,
  417. self.authenticated_entity,
  418. int(time.time() * 1000) - self.start_time,
  419. int(ru_utime * 1000),
  420. int(ru_stime * 1000),
  421. int(db_txn_duration * 1000),
  422. int(db_txn_count),
  423. self.sentLength,
  424. self.code,
  425. self.method,
  426. self.get_redacted_uri(),
  427. self.clientproto,
  428. self.get_user_agent(),
  429. )
  430. @contextlib.contextmanager
  431. def processing(self):
  432. self.started_processing()
  433. yield
  434. self.finished_processing()
  435. class XForwardedForRequest(SynapseRequest):
  436. def __init__(self, *args, **kw):
  437. SynapseRequest.__init__(self, *args, **kw)
  438. """
  439. Add a layer on top of another request that only uses the value of an
  440. X-Forwarded-For header as the result of C{getClientIP}.
  441. """
  442. def getClientIP(self):
  443. """
  444. @return: The client address (the first address) in the value of the
  445. I{X-Forwarded-For header}. If the header is not present, return
  446. C{b"-"}.
  447. """
  448. return self.requestHeaders.getRawHeaders(
  449. b"x-forwarded-for", [b"-"])[0].split(b",")[0].strip()
  450. class SynapseRequestFactory(object):
  451. def __init__(self, site, x_forwarded_for):
  452. self.site = site
  453. self.x_forwarded_for = x_forwarded_for
  454. def __call__(self, *args, **kwargs):
  455. if self.x_forwarded_for:
  456. return XForwardedForRequest(self.site, *args, **kwargs)
  457. else:
  458. return SynapseRequest(self.site, *args, **kwargs)
  459. class SynapseSite(Site):
  460. """
  461. Subclass of a twisted http Site that does access logging with python's
  462. standard logging
  463. """
  464. def __init__(self, logger_name, site_tag, config, resource, *args, **kwargs):
  465. Site.__init__(self, resource, *args, **kwargs)
  466. self.site_tag = site_tag
  467. proxied = config.get("x_forwarded", False)
  468. self.requestFactory = SynapseRequestFactory(self, proxied)
  469. self.access_logger = logging.getLogger(logger_name)
  470. def log(self, request):
  471. pass
  472. def create_resource_tree(desired_tree, redirect_root_to_web_client=True):
  473. """Create the resource tree for this Home Server.
  474. This in unduly complicated because Twisted does not support putting
  475. child resources more than 1 level deep at a time.
  476. Args:
  477. web_client (bool): True to enable the web client.
  478. redirect_root_to_web_client (bool): True to redirect '/' to the
  479. location of the web client. This does nothing if web_client is not
  480. True.
  481. """
  482. if redirect_root_to_web_client and WEB_CLIENT_PREFIX in desired_tree:
  483. root_resource = RootRedirect(WEB_CLIENT_PREFIX)
  484. else:
  485. root_resource = Resource()
  486. # ideally we'd just use getChild and putChild but getChild doesn't work
  487. # unless you give it a Request object IN ADDITION to the name :/ So
  488. # instead, we'll store a copy of this mapping so we can actually add
  489. # extra resources to existing nodes. See self._resource_id for the key.
  490. resource_mappings = {}
  491. for full_path, res in desired_tree.items():
  492. logger.info("Attaching %s to path %s", res, full_path)
  493. last_resource = root_resource
  494. for path_seg in full_path.split('/')[1:-1]:
  495. if path_seg not in last_resource.listNames():
  496. # resource doesn't exist, so make a "dummy resource"
  497. child_resource = Resource()
  498. last_resource.putChild(path_seg, child_resource)
  499. res_id = _resource_id(last_resource, path_seg)
  500. resource_mappings[res_id] = child_resource
  501. last_resource = child_resource
  502. else:
  503. # we have an existing Resource, use that instead.
  504. res_id = _resource_id(last_resource, path_seg)
  505. last_resource = resource_mappings[res_id]
  506. # ===========================
  507. # now attach the actual desired resource
  508. last_path_seg = full_path.split('/')[-1]
  509. # if there is already a resource here, thieve its children and
  510. # replace it
  511. res_id = _resource_id(last_resource, last_path_seg)
  512. if res_id in resource_mappings:
  513. # there is a dummy resource at this path already, which needs
  514. # to be replaced with the desired resource.
  515. existing_dummy_resource = resource_mappings[res_id]
  516. for child_name in existing_dummy_resource.listNames():
  517. child_res_id = _resource_id(
  518. existing_dummy_resource, child_name
  519. )
  520. child_resource = resource_mappings[child_res_id]
  521. # steal the children
  522. res.putChild(child_name, child_resource)
  523. # finally, insert the desired resource in the right place
  524. last_resource.putChild(last_path_seg, res)
  525. res_id = _resource_id(last_resource, last_path_seg)
  526. resource_mappings[res_id] = res
  527. return root_resource
  528. def _resource_id(resource, path_seg):
  529. """Construct an arbitrary resource ID so you can retrieve the mapping
  530. later.
  531. If you want to represent resource A putChild resource B with path C,
  532. the mapping should looks like _resource_id(A,C) = B.
  533. Args:
  534. resource (Resource): The *parent* Resourceb
  535. path_seg (str): The name of the child Resource to be attached.
  536. Returns:
  537. str: A unique string which can be a key to the child Resource.
  538. """
  539. return "%s-%s" % (resource, path_seg)
  540. def run(hs):
  541. PROFILE_SYNAPSE = False
  542. if PROFILE_SYNAPSE:
  543. def profile(func):
  544. from cProfile import Profile
  545. from threading import current_thread
  546. def profiled(*args, **kargs):
  547. profile = Profile()
  548. profile.enable()
  549. func(*args, **kargs)
  550. profile.disable()
  551. ident = current_thread().ident
  552. profile.dump_stats("/tmp/%s.%s.%i.pstat" % (
  553. hs.hostname, func.__name__, ident
  554. ))
  555. return profiled
  556. from twisted.python.threadpool import ThreadPool
  557. ThreadPool._worker = profile(ThreadPool._worker)
  558. reactor.run = profile(reactor.run)
  559. start_time = hs.get_clock().time()
  560. @defer.inlineCallbacks
  561. def phone_stats_home():
  562. logger.info("Gathering stats for reporting")
  563. now = int(hs.get_clock().time())
  564. uptime = int(now - start_time)
  565. if uptime < 0:
  566. uptime = 0
  567. stats = {}
  568. stats["homeserver"] = hs.config.server_name
  569. stats["timestamp"] = now
  570. stats["uptime_seconds"] = uptime
  571. stats["total_users"] = yield hs.get_datastore().count_all_users()
  572. room_count = yield hs.get_datastore().get_room_count()
  573. stats["total_room_count"] = room_count
  574. stats["daily_active_users"] = yield hs.get_datastore().count_daily_users()
  575. daily_messages = yield hs.get_datastore().count_daily_messages()
  576. if daily_messages is not None:
  577. stats["daily_messages"] = daily_messages
  578. logger.info("Reporting stats to matrix.org: %s" % (stats,))
  579. try:
  580. yield hs.get_simple_http_client().put_json(
  581. "https://matrix.org/report-usage-stats/push",
  582. stats
  583. )
  584. except Exception as e:
  585. logger.warn("Error reporting stats: %s", e)
  586. if hs.config.report_stats:
  587. phone_home_task = task.LoopingCall(phone_stats_home)
  588. logger.info("Scheduling stats reporting for 24 hour intervals")
  589. phone_home_task.start(60 * 60 * 24, now=False)
  590. def in_thread():
  591. # Uncomment to enable tracing of log context changes.
  592. # sys.settrace(logcontext_tracer)
  593. with LoggingContext("run"):
  594. change_resource_limit(hs.config.soft_file_limit)
  595. reactor.run()
  596. if hs.config.daemonize:
  597. if hs.config.print_pidfile:
  598. print (hs.config.pid_file)
  599. daemon = Daemonize(
  600. app="synapse-homeserver",
  601. pid=hs.config.pid_file,
  602. action=lambda: in_thread(),
  603. auto_close_fds=False,
  604. verbose=True,
  605. logger=logger,
  606. )
  607. daemon.start()
  608. else:
  609. in_thread()
  610. def main():
  611. with LoggingContext("main"):
  612. # check base requirements
  613. check_requirements()
  614. hs = setup(sys.argv[1:])
  615. run(hs)
  616. if __name__ == '__main__':
  617. main()