-
-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feature] Added optional clean insights measurement collection #360 #365
Changes from all commits
d5440f1
a9bd59e
072bc46
bb6011b
1c22c75
e55322c
6d0fd7f
a9d0cd4
4eddd6a
b05758f
35d0cda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from django.apps import AppConfig | ||
from django.conf import settings | ||
from django.db.models.signals import post_migrate | ||
|
||
|
||
class MeasurementsConfig(AppConfig): | ||
default_auto_field = 'django.db.models.BigAutoField' | ||
name = 'openwisp_utils.measurements' | ||
app_label = 'openwisp_measurements' | ||
|
||
def ready(self): | ||
super().ready() | ||
self.connect_post_migrate_signal() | ||
|
||
def connect_post_migrate_signal(self): | ||
post_migrate.connect(self.post_migrate_receiver, sender=self) | ||
|
||
@classmethod | ||
def post_migrate_receiver(cls, **kwargs): | ||
if getattr(settings, 'DEBUG', False): | ||
# Do not send usage metrics in debug mode | ||
# i.e. when running tests. | ||
return | ||
|
||
from .tasks import send_usage_metrics | ||
|
||
is_new_install = False | ||
if kwargs.get('plan'): | ||
migration, migration_rolled_back = kwargs['plan'][0] | ||
is_new_install = ( | ||
migration_rolled_back is False | ||
and str(migration) == 'contenttypes.0001_initial' | ||
) | ||
|
||
# If the migration plan includes creating table | ||
# for the ContentType model, then the installation is | ||
# treated as a new installation. | ||
if is_new_install: | ||
# This is a new installation | ||
send_usage_metrics.delay() | ||
else: | ||
send_usage_metrics.delay(upgrade_only=True) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Generated by Django 4.2.7 on 2023-12-06 15:30 | ||
|
||
from django.db import migrations, models | ||
import django.utils.timezone | ||
import model_utils.fields | ||
import uuid | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="OpenwispVersion", | ||
fields=[ | ||
( | ||
"id", | ||
models.UUIDField( | ||
default=uuid.uuid4, | ||
editable=False, | ||
primary_key=True, | ||
serialize=False, | ||
), | ||
), | ||
( | ||
"created", | ||
model_utils.fields.AutoCreatedField( | ||
default=django.utils.timezone.now, | ||
editable=False, | ||
verbose_name="created", | ||
), | ||
), | ||
("module_version", models.JSONField(blank=True, default=dict)), | ||
], | ||
options={ | ||
"ordering": ("-created",), | ||
}, | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
from django.db import models | ||
from openwisp_utils.base import TimeStampedEditableModel | ||
from packaging.version import parse as parse_version | ||
|
||
|
||
class OpenwispVersion(TimeStampedEditableModel): | ||
modified = None | ||
module_version = models.JSONField(default=dict, blank=True) | ||
|
||
class Meta: | ||
ordering = ('-created',) | ||
|
||
@classmethod | ||
def is_new_installation(cls): | ||
return not cls.objects.exists() | ||
|
||
@classmethod | ||
def get_upgraded_modules(cls, current_versions): | ||
""" | ||
Retrieves a dictionary of upgraded modules based on current versions. | ||
Also updates the OpenwispVersion object with the new versions. | ||
|
||
Args: | ||
current_versions (dict): A dictionary containing the current versions of modules. | ||
|
||
Returns: | ||
dict: A dictionary containing the upgraded modules and their versions. | ||
""" | ||
openwisp_version = cls.objects.first() | ||
if not openwisp_version: | ||
cls.objects.create(module_version=current_versions) | ||
return {} | ||
old_versions = openwisp_version.module_version | ||
upgraded_modules = {} | ||
for module, version in current_versions.items(): | ||
if module in old_versions and parse_version( | ||
old_versions[module] | ||
) < parse_version(version): | ||
upgraded_modules[module] = version | ||
openwisp_version.module_version[module] = version | ||
if upgraded_modules: | ||
# Save the new versions in a new object | ||
OpenwispVersion.objects.create( | ||
module_version=openwisp_version.module_version | ||
) | ||
return upgraded_modules |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import logging | ||
|
||
from celery import shared_task | ||
from openwisp_utils.admin_theme.system_info import ( | ||
get_enabled_openwisp_modules, | ||
get_openwisp_version, | ||
get_os_details, | ||
) | ||
|
||
from ..tasks import OpenwispCeleryTask | ||
from ..utils import retryable_request | ||
from .models import OpenwispVersion | ||
from .utils import _get_events, get_openwisp_module_metrics, get_os_detail_metrics | ||
|
||
USER_METRIC_COLLECTION_URL = 'https://analytics.openwisp.io/cleaninsights.php' | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def post_usage_metrics(events): | ||
try: | ||
response = retryable_request( | ||
'post', | ||
url=USER_METRIC_COLLECTION_URL, | ||
json={ | ||
'idsite': 5, | ||
'events': events, | ||
}, | ||
max_retries=10, | ||
) | ||
assert response.status_code == 204 | ||
except Exception as error: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are you absolutely sure this code works also when the server does not reply at all? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have tested the following scenarios:
I have added a test case for this https://github.com/openwisp/openwisp-utils/pull/365/files#diff-118b67d11be9c05a77bccfedd814f41974ca3fb2bfd21eb010350faf22d59d3bR180-R196 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used this code for the mock server from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
class MyHandler(SimpleHTTPRequestHandler):
def do_GET(self):
import time
time.sleep(40000000)
def run_server():
server_address = ('', 8000)
httpd = TCPServer(server_address, MyHandler)
print(f"Server running on http://localhost:{server_address[1]}")
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
finally:
httpd.server_close()
if __name__ == "__main__":
run_server() |
||
if isinstance(error, AssertionError): | ||
message = f'HTTP {response.status_code} Response' | ||
else: | ||
message = str(error) | ||
logger.error( | ||
f'Collection of usage metrics failed, max retries exceeded. Error: {message}' | ||
) | ||
|
||
|
||
@shared_task(base=OpenwispCeleryTask) | ||
def send_usage_metrics(upgrade_only=False): | ||
current_versions = get_enabled_openwisp_modules() | ||
current_versions.update({'OpenWISP Version': get_openwisp_version()}) | ||
metrics = [] | ||
metrics.extend(get_os_detail_metrics(get_os_details())) | ||
if OpenwispVersion.is_new_installation(): | ||
metrics.extend(_get_events('Install', current_versions)) | ||
OpenwispVersion.objects.create(module_version=current_versions) | ||
else: | ||
upgraded_modules = OpenwispVersion.get_upgraded_modules(current_versions) | ||
metrics.extend(_get_events('Upgrade', upgraded_modules)) | ||
if not upgrade_only: | ||
metrics.extend(get_openwisp_module_metrics(current_versions)) | ||
post_usage_metrics(metrics) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a paragraph introducing and linking to cleaninsights.