Skip to content

Commit a715b33

Browse files
committed
Changes to get some test performance data
1 parent 6b2efe3 commit a715b33

File tree

4 files changed

+251
-0
lines changed

4 files changed

+251
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Generated by Claude Sonnet 4
2+
import logging
3+
import time
4+
from contextlib import contextmanager
5+
from functools import wraps
6+
7+
logger = logging.getLogger('ansible_base.performance')
8+
9+
10+
@contextmanager
11+
def measure_time(operation_name):
12+
"""Context manager to measure and log execution time of operations."""
13+
start_time = time.perf_counter()
14+
try:
15+
yield
16+
finally:
17+
end_time = time.perf_counter()
18+
duration = end_time - start_time
19+
logger.info(f"[PERFORMANCE] {operation_name}: {duration:.4f}s")
20+
21+
22+
def measure_post_migrate_signal(func):
23+
"""Decorator to measure post_migrate signal handler execution time."""
24+
@wraps(func)
25+
def wrapper(*args, **kwargs):
26+
func_name = f"{func.__module__}.{func.__name__}"
27+
with measure_time(f"post_migrate: {func_name}"):
28+
return func(*args, **kwargs)
29+
return wrapper
30+
31+
32+
def patch_post_migrate_handlers():
33+
"""
34+
Monkey patch existing post_migrate signal handlers to add performance measurement.
35+
This should be called early in Django startup to capture all handlers.
36+
"""
37+
# Import the handlers we want to measure
38+
try:
39+
from ansible_base.rbac.management import create_dab_permissions
40+
from ansible_base.rbac.triggers import post_migration_rbac_setup
41+
from ansible_base.resource_registry.apps import initialize_resources, connect_resource_signals
42+
43+
# Patch the handlers
44+
import ansible_base.rbac.management
45+
import ansible_base.rbac.triggers
46+
import ansible_base.resource_registry.apps
47+
48+
ansible_base.rbac.management.create_dab_permissions = measure_post_migrate_signal(create_dab_permissions)
49+
ansible_base.rbac.triggers.post_migration_rbac_setup = measure_post_migrate_signal(post_migration_rbac_setup)
50+
ansible_base.resource_registry.apps.initialize_resources = measure_post_migrate_signal(initialize_resources)
51+
ansible_base.resource_registry.apps.connect_resource_signals = measure_post_migrate_signal(connect_resource_signals)
52+
53+
logger.info("[PERFORMANCE] Post-migrate signal handlers patched for performance measurement")
54+
55+
except ImportError as e:
56+
logger.warning(f"[PERFORMANCE] Could not patch post_migrate handlers: {e}")
57+
58+
59+
def enable_performance_logging():
60+
"""Enable performance logging by setting appropriate log levels."""
61+
perf_logger = logging.getLogger('ansible_base.performance')
62+
perf_logger.setLevel(logging.INFO)
63+
64+
# Add a handler if none exists and prevent propagation to avoid duplicates
65+
if not perf_logger.handlers:
66+
handler = logging.StreamHandler()
67+
formatter = logging.Formatter('[%(asctime)s] %(levelname)s %(name)s: %(message)s')
68+
handler.setFormatter(formatter)
69+
perf_logger.addHandler(handler)
70+
perf_logger.propagate = False
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Generated by Claude Sonnet 4
2+
"""
3+
Test configuration to enable post_migrate performance measurement.
4+
5+
To use this in your tests or CI, add to your Django settings:
6+
7+
# Enable post_migrate performance measurement
8+
ANSIBLE_BASE_MEASURE_POST_MIGRATE_PERFORMANCE = True
9+
10+
# Optional: Configure logging level for performance measurements
11+
LOGGING = {
12+
'version': 1,
13+
'disable_existing_loggers': False,
14+
'formatters': {
15+
'performance': {
16+
'format': '[{asctime}] {levelname} {name}: {message}',
17+
'style': '{',
18+
},
19+
},
20+
'handlers': {
21+
'performance_console': {
22+
'class': 'logging.StreamHandler',
23+
'formatter': 'performance',
24+
},
25+
},
26+
'loggers': {
27+
'ansible_base.performance': {
28+
'handlers': ['performance_console'],
29+
'level': 'INFO',
30+
'propagate': False,
31+
},
32+
},
33+
}
34+
35+
Example output when enabled:
36+
[2025-08-21 10:30:15,123] INFO ansible_base.performance: [PERFORMANCE] post_migrate: ansible_base.rbac.management.create_dab_permissions: 0.0234s
37+
[2025-08-21 10:30:15,456] INFO ansible_base.performance: [PERFORMANCE] post_migrate: ansible_base.rbac.triggers.post_migration_rbac_setup: 0.1234s
38+
[2025-08-21 10:30:15,678] INFO ansible_base.performance: [PERFORMANCE] post_migrate: ansible_base.resource_registry.apps.initialize_resources: 0.0456s
39+
[2025-08-21 10:30:15,789] INFO ansible_base.performance: [PERFORMANCE] post_migrate: ansible_base.resource_registry.apps.connect_resource_signals: 0.0123s
40+
"""
41+
42+
# Example settings that can be imported in test configurations
43+
PERFORMANCE_MEASUREMENT_SETTINGS = {
44+
'ANSIBLE_BASE_MEASURE_POST_MIGRATE_PERFORMANCE': True,
45+
'LOGGING': {
46+
'version': 1,
47+
'disable_existing_loggers': False,
48+
'formatters': {
49+
'performance': {
50+
'format': '[{asctime}] {levelname} {name}: {message}',
51+
'style': '{',
52+
},
53+
},
54+
'handlers': {
55+
'performance_console': {
56+
'class': 'logging.StreamHandler',
57+
'formatter': 'performance',
58+
},
59+
},
60+
'loggers': {
61+
'ansible_base.performance': {
62+
'handlers': ['performance_console'],
63+
'level': 'INFO',
64+
'propagate': False,
65+
},
66+
},
67+
}
68+
}

ansible_base/rbac/permission_registry.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ def call_when_apps_ready(self, apps, app_config) -> None:
138138
# This will lock-down the registry, raising an error for any other registrations
139139
self.apps_ready = True
140140

141+
# Enable performance measurement for post_migrate signals if requested
142+
from django.conf import settings as django_settings
143+
if getattr(django_settings, 'ANSIBLE_BASE_MEASURE_POST_MIGRATE_PERFORMANCE', False):
144+
from ansible_base.lib.utils.performance import patch_post_migrate_handlers, enable_performance_logging
145+
enable_performance_logging()
146+
patch_post_migrate_handlers()
147+
141148
# Do no specify sender for create_dab_permissions, because that is passed as app_config
142149
# and we want to create permissions for external apps, not the dab_rbac app
143150
post_migrate.connect(
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Generated by Claude Sonnet 4
2+
import logging
3+
import time
4+
from unittest.mock import patch
5+
6+
import pytest
7+
from django.test import TestCase, override_settings
8+
9+
from ansible_base.lib.utils.performance import (
10+
enable_performance_logging,
11+
measure_post_migrate_signal,
12+
measure_time,
13+
patch_post_migrate_handlers,
14+
)
15+
16+
17+
class TestPerformanceMeasurement(TestCase):
18+
"""Test the performance measurement utilities."""
19+
20+
def test_measure_time_context_manager(self):
21+
"""Test that measure_time context manager logs execution time."""
22+
with self.assertLogs('ansible_base.performance', level='INFO') as cm:
23+
with measure_time("test_operation"):
24+
time.sleep(0.01) # Small delay to ensure measurable time
25+
26+
self.assertEqual(len(cm.output), 1)
27+
self.assertIn("[PERFORMANCE] test_operation:", cm.output[0])
28+
# Should show some positive time duration
29+
self.assertRegex(cm.output[0], r'\d+\.\d{4}s')
30+
31+
def test_measure_post_migrate_signal_decorator(self):
32+
"""Test that the post_migrate signal decorator works correctly."""
33+
34+
@measure_post_migrate_signal
35+
def dummy_post_migrate_handler(sender, **kwargs):
36+
time.sleep(0.01) # Small delay
37+
return "test_result"
38+
39+
with self.assertLogs('ansible_base.performance', level='INFO') as cm:
40+
result = dummy_post_migrate_handler(sender=None)
41+
42+
self.assertEqual(result, "test_result")
43+
self.assertEqual(len(cm.output), 1)
44+
self.assertIn("post_migrate: test_app.tests.lib.utils.test_performance.dummy_post_migrate_handler", cm.output[0])
45+
46+
def test_enable_performance_logging(self):
47+
"""Test that enable_performance_logging sets up the logger correctly."""
48+
# Get the logger before enabling
49+
logger = logging.getLogger('ansible_base.performance')
50+
original_level = logger.level
51+
original_handlers = list(logger.handlers)
52+
53+
try:
54+
enable_performance_logging()
55+
56+
# Check that the logger is set to INFO level
57+
self.assertEqual(logger.level, logging.INFO)
58+
59+
# Check that a handler was added if none existed
60+
if not original_handlers:
61+
self.assertTrue(len(logger.handlers) > 0)
62+
63+
finally:
64+
# Restore original state
65+
logger.setLevel(original_level)
66+
logger.handlers = original_handlers
67+
68+
@patch('ansible_base.rbac.management.create_dab_permissions')
69+
@patch('ansible_base.rbac.triggers.post_migration_rbac_setup')
70+
@patch('ansible_base.resource_registry.apps.initialize_resources')
71+
@patch('ansible_base.resource_registry.apps.connect_resource_signals')
72+
def test_patch_post_migrate_handlers(self, mock_connect, mock_init, mock_rbac, mock_perms):
73+
"""Test that patch_post_migrate_handlers patches the handlers."""
74+
# Mock the original functions
75+
def original_func():
76+
return "original"
77+
78+
mock_connect.return_value = original_func
79+
mock_init.return_value = original_func
80+
mock_rbac.return_value = original_func
81+
mock_perms.return_value = original_func
82+
83+
with self.assertLogs('ansible_base.performance', level='INFO') as cm:
84+
patch_post_migrate_handlers()
85+
86+
# Check that the patching was logged
87+
patch_logged = any("Post-migrate signal handlers patched" in output for output in cm.output)
88+
self.assertTrue(patch_logged, "Patching should have been logged")
89+
90+
@override_settings(ANSIBLE_BASE_MEASURE_POST_MIGRATE_PERFORMANCE=True)
91+
def test_performance_measurement_setting(self):
92+
"""Test that the setting enables performance measurement."""
93+
from django.conf import settings
94+
self.assertTrue(getattr(settings, 'ANSIBLE_BASE_MEASURE_POST_MIGRATE_PERFORMANCE', False))
95+
96+
97+
class TestPerformanceMeasurementIntegration(TestCase):
98+
"""Integration tests for performance measurement with Django signals."""
99+
100+
@override_settings(ANSIBLE_BASE_MEASURE_POST_MIGRATE_PERFORMANCE=True)
101+
def test_performance_measurement_with_setting_enabled(self):
102+
"""Test that performance measurement works when the setting is enabled."""
103+
# This test verifies the integration works, but doesn't run actual migrations
104+
# as that would be too complex for a unit test
105+
from django.conf import settings
106+
self.assertTrue(getattr(settings, 'ANSIBLE_BASE_MEASURE_POST_MIGRATE_PERFORMANCE', False))

0 commit comments

Comments
 (0)