analytics-platform 0.3.2__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/PKG-INFO +5 -2
  2. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/README.md +4 -1
  3. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/__init__.py +1 -1
  4. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/core.py +43 -2
  5. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform.egg-info/PKG-INFO +5 -2
  6. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform.egg-info/SOURCES.txt +1 -0
  7. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/pyproject.toml +1 -1
  8. analytics_platform-0.3.3/tests/test_checkin.py +29 -0
  9. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/LICENSE +0 -0
  10. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/analytics.py +0 -0
  11. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/audit.py +0 -0
  12. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/crawlers.py +0 -0
  13. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/django.py +0 -0
  14. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/errors.py +0 -0
  15. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/firewall.py +0 -0
  16. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/flags.py +0 -0
  17. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/forms.py +0 -0
  18. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/inventory.py +0 -0
  19. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/maintenance.py +0 -0
  20. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/performance.py +0 -0
  21. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/posture.py +0 -0
  22. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/proxy.py +0 -0
  23. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/request_rules.py +0 -0
  24. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform/resources.py +0 -0
  25. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform.egg-info/dependency_links.txt +0 -0
  26. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/analytics_platform.egg-info/top_level.txt +0 -0
  27. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/setup.cfg +0 -0
  28. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/tests/test_crawlers.py +0 -0
  29. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/tests/test_errors.py +0 -0
  30. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/tests/test_firewall.py +0 -0
  31. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/tests/test_proxy.py +0 -0
  32. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/tests/test_request_rules.py +0 -0
  33. {analytics_platform-0.3.2 → analytics_platform-0.3.3}/tests/test_resources.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: analytics-platform
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Firewall, error monitoring and server analytics for your Python server, run from your analytics dashboard.
5
5
  Author: steviec87
6
6
  License-Expression: MIT
@@ -187,7 +187,10 @@ Several services on one site: `Platform(..., server_name="worker")`.
187
187
  **Several apps on one site** (microservices, a worker, a Node service next to
188
188
  this one): install the package in each, with the same `site_id`, `secret` and
189
189
  `endpoint`, and a `server_name` of its own. Errors, Server speed, Crawlers and
190
- Security → Updates then tell the apps apart. Without one, each app is named
190
+ Security → Updates then tell the apps apart, and Health → All systems lists
191
+ each with a light: the package checks in about once a minute, so an app that
192
+ stops reads "Stopped" within ten minutes (`sdk/protocols/checkin.md`).
193
+ Without one, each app is named
191
194
  after its machine: two apps on one machine are reported as one, and only the
192
195
  last package list sent is kept. A service only your other apps call takes
193
196
  `firewall=False, proxy=False, maintenance=False` (its requests all come from
@@ -161,7 +161,10 @@ Several services on one site: `Platform(..., server_name="worker")`.
161
161
  **Several apps on one site** (microservices, a worker, a Node service next to
162
162
  this one): install the package in each, with the same `site_id`, `secret` and
163
163
  `endpoint`, and a `server_name` of its own. Errors, Server speed, Crawlers and
164
- Security → Updates then tell the apps apart. Without one, each app is named
164
+ Security → Updates then tell the apps apart, and Health → All systems lists
165
+ each with a light: the package checks in about once a minute, so an app that
166
+ stops reads "Stopped" within ten minutes (`sdk/protocols/checkin.md`).
167
+ Without one, each app is named
165
168
  after its machine: two apps on one machine are reported as one, and only the
166
169
  last package list sent is kept. A service only your other apps call takes
167
170
  `firewall=False, proxy=False, maintenance=False` (its requests all come from
@@ -81,7 +81,7 @@ class Platform:
81
81
  performance=True, crawlers=True, forms=True, headers=True, audit=True, flags=True, maintenance=True,
82
82
  inventory=True,
83
83
  posture=True, resources=True, proxy=True, server_name=None):
84
- self.core = Core(site_id, secret, endpoint, debug)
84
+ self.core = Core(site_id, secret, endpoint, debug, server_name=server_name)
85
85
  if not self.core.valid:
86
86
  print("[analytics-platform] site_id, secret and endpoint are required; nothing is sent and every request passes")
87
87
  settings = {"firewall": firewall, "errors": errors, "analytics": analytics, "performance": performance,
@@ -10,13 +10,16 @@ start again in a forked worker (gunicorn, uWSGI), where threads don't survive.
10
10
  import atexit
11
11
  import json
12
12
  import os
13
+ import platform
13
14
  import re
15
+ import socket
14
16
  import threading
17
+ import time
15
18
  import urllib.error
16
19
  import urllib.parse
17
20
  import urllib.request
18
21
 
19
- VERSION = "0.3.2"
22
+ VERSION = "0.3.3"
20
23
  TIMEOUT = 5
21
24
  MIN_SYNC = 30
22
25
  RETRY = 60
@@ -100,9 +103,30 @@ class Periodic:
100
103
  thread.join(wait)
101
104
 
102
105
 
106
+ def _rss_mb():
107
+ """This process's resident memory in MB, where the platform says (Linux
108
+ /proc; elsewhere the peak, from resource); None when neither can."""
109
+ try:
110
+ with open("/proc/self/statm") as statm:
111
+ return round(int(statm.read().split()[1]) * os.sysconf("SC_PAGE_SIZE") / 1048576)
112
+ except Exception:
113
+ pass
114
+ try:
115
+ import resource
116
+ import sys
117
+ peak = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
118
+ return round(peak / 1048576 if sys.platform == "darwin" else peak / 1024)
119
+ except Exception:
120
+ return None
121
+
122
+
103
123
  class Core:
104
- def __init__(self, site_id, secret, endpoint, debug=False):
124
+ def __init__(self, site_id, secret, endpoint, debug=False, server_name=None):
105
125
  self.site_id = str(site_id or "")
126
+ # The name this service checks in under (X-Platform-App, on the switches
127
+ # request): server_name, as every module sends it, else the machine's.
128
+ self.server_name = str(server_name or socket.gethostname())[:100]
129
+ self._started = time.time()
106
130
  self.secret = str(secret or "")
107
131
  self.endpoint = str(endpoint or "").rstrip("/")
108
132
  self.valid = bool(re.fullmatch(r"[a-fA-F0-9]{24}", self.site_id) and self.secret and self.endpoint)
@@ -154,8 +178,25 @@ class Core:
154
178
  """False only when the dashboard turned the module off."""
155
179
  return self._switches.get(name) is not False
156
180
 
181
+ def _check_in(self):
182
+ """This service's check-in, form-encoded so any name is plain ASCII:
183
+ Health -> All systems shows it running and when it was last heard from.
184
+ None when it can't be built: the switches still sync, without it."""
185
+ try:
186
+ fields = {"server": self.server_name, "kind": "process", "pid": os.getpid(),
187
+ "up_s": int(time.time() - self._started), "runtime": "Python " + platform.python_version()}
188
+ rss = _rss_mb()
189
+ if rss is not None:
190
+ fields["rss_mb"] = rss
191
+ return urllib.parse.urlencode(fields) if self.server_name else None
192
+ except Exception:
193
+ return None
194
+
157
195
  def _sync_switches(self):
158
196
  headers = {"X-Platform-Modules": ",".join(self._loaded)}
197
+ app = self._check_in()
198
+ if app:
199
+ headers["X-Platform-App"] = app
159
200
  if self._etag:
160
201
  headers["If-None-Match"] = self._etag
161
202
  res = self.send("core", "/events/platform/config?site_id=" + self.site_id, method="GET", auth="bearer", headers=headers)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: analytics-platform
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Firewall, error monitoring and server analytics for your Python server, run from your analytics dashboard.
5
5
  Author: steviec87
6
6
  License-Expression: MIT
@@ -187,7 +187,10 @@ Several services on one site: `Platform(..., server_name="worker")`.
187
187
  **Several apps on one site** (microservices, a worker, a Node service next to
188
188
  this one): install the package in each, with the same `site_id`, `secret` and
189
189
  `endpoint`, and a `server_name` of its own. Errors, Server speed, Crawlers and
190
- Security → Updates then tell the apps apart. Without one, each app is named
190
+ Security → Updates then tell the apps apart, and Health → All systems lists
191
+ each with a light: the package checks in about once a minute, so an app that
192
+ stops reads "Stopped" within ten minutes (`sdk/protocols/checkin.md`).
193
+ Without one, each app is named
191
194
  after its machine: two apps on one machine are reported as one, and only the
192
195
  last package list sent is kept. A service only your other apps call takes
193
196
  `firewall=False, proxy=False, maintenance=False` (its requests all come from
@@ -22,6 +22,7 @@ analytics_platform.egg-info/PKG-INFO
22
22
  analytics_platform.egg-info/SOURCES.txt
23
23
  analytics_platform.egg-info/dependency_links.txt
24
24
  analytics_platform.egg-info/top_level.txt
25
+ tests/test_checkin.py
25
26
  tests/test_crawlers.py
26
27
  tests/test_errors.py
27
28
  tests/test_firewall.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "analytics-platform"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Firewall, error monitoring and server analytics for your Python server, run from your analytics dashboard."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -0,0 +1,29 @@
1
+ """The check-in on the switches request (X-Platform-App): what names this
2
+ service on Health -> All systems."""
3
+ import os
4
+ import unittest
5
+ import urllib.parse
6
+ from unittest import mock
7
+
8
+ from analytics_platform.core import Core
9
+
10
+
11
+ class CheckInTest(unittest.TestCase):
12
+ def test_switches_request_says_which_service_asks_and_that_it_runs(self):
13
+ core = Core("a" * 24, "s", "http://collector", server_name="api wörker")
14
+ with mock.patch.object(core, "send", return_value=None) as send:
15
+ core._sync_switches()
16
+ header = send.call_args.kwargs["headers"]["X-Platform-App"]
17
+ fields = dict(urllib.parse.parse_qsl(header))
18
+ self.assertEqual(fields["server"], "api wörker")
19
+ self.assertEqual(fields["kind"], "process")
20
+ self.assertEqual(fields["pid"], str(os.getpid()))
21
+ self.assertTrue(fields["runtime"].startswith("Python "))
22
+ self.assertTrue(header.isascii())
23
+
24
+ def test_default_name_is_the_machines(self):
25
+ self.assertTrue(Core("a" * 24, "s", "http://collector").server_name)
26
+
27
+
28
+ if __name__ == "__main__":
29
+ unittest.main()