File indexing completed on 2026-11-09 10:47:30
0001 """Ratios, incident comparisons and history replacement; no database writes."""
0002 from datetime import date, timedelta
0003 from unittest.mock import patch
0004
0005 from django.test import SimpleTestCase
0006
0007 from monitor_app.compute_efficiency import build, build_current, dashboard_data, display_data, merge_days
0008
0009
0010 class ComputeEfficiencyTests(SimpleTestCase):
0011 def test_dashboard_months_sum_hours_and_keep_unmeasured_months(self):
0012 result = dashboard_data({'daily': [
0013 ['2026-05-01', 'a', False, '', 90, 100, 10, 0],
0014 ['2026-05-02', 'b', False, '', 0, 900, 30, 1],
0015 ['2026-06-01', 'a', False, '', 0, 60, 2, 0],
0016 ]}, None)
0017 may, june, july = result['months'][:3]
0018 self.assertEqual((may['efficiency'], may['jobs'], may['missing']), (9, 40, 1))
0019 self.assertEqual(june['efficiency'], 0)
0020 self.assertIsNone(july['efficiency'])
0021 self.assertIsNone(result['current'])
0022
0023 def test_current_window_is_exactly_24_hours_and_sums_core_hours(self):
0024 with patch('monitor_app.compute_efficiency.query_days', return_value=[
0025 ['2026-10-24', 'a', False, '', 90, 100, 10, 0],
0026 ['2026-10-25', 'b', False, '', 0, 900, 30, 0],
0027 ]) as query:
0028 result = build_current()
0029 start, end = query.call_args.args
0030 self.assertEqual(end - start, timedelta(hours=24))
0031 self.assertIsNotNone(start.tzinfo)
0032 self.assertEqual((result['efficiency'], result['jobs']), (9, 40))
0033 self.assertEqual(result['core_hours_per_day'], 1000)
0034 self.assertEqual(result['productive_cores'], 90 / 24)
0035
0036 def test_site_delivery_counts_failed_hours_and_uses_elapsed_calendar_days(self):
0037 rows = [
0038 ['2026-05-01', 'BNL_OSG_EPIC_PROD_1', True, 'site:A', 240, 480, 10, 0],
0039 ['2026-05-02', 'BNL_OSG_PanDA_1', True, 'site:B', 120, 240, 5, 0],
0040 ['2026-05-02', 'NERSC_Perlmutter_epic_test', False, '', 240, 960, 20, 0],
0041 ['2026-05-02', 'UM_GREX_PanDA_1', False, '', 480, 480, 20, 0],
0042 ]
0043 result = dashboard_data({'daily': rows, 'through': '2026-05-16T12:00:00+00:00'}, None)
0044 sites = {site['id']: site['months'][0] for site in result['sites']}
0045 self.assertEqual(sites['osg']['ended'], 720)
0046 self.assertEqual(sites['perlmutter']['efficiency'], 25)
0047 self.assertEqual(sites['grex']['efficiency'], 100)
0048 for row in [result['months'][0], *sites.values()]:
0049 self.assertEqual(row['days'], 15.5)
0050 self.assertAlmostEqual(row['core_hours_per_day'], row['ended'] / 15.5)
0051 self.assertAlmostEqual(row['productive_cores'], row['finished'] / (15.5 * 24))
0052 self.assertAlmostEqual(row['productive_cores'],
0053 row['core_hours_per_day'] * row['efficiency'] / 100 / 24)
0054
0055 def data(self, rows, **options):
0056 with patch('monitor_app.compute_efficiency._queue_labels', return_value={
0057 'grex': 'GREX', 'NERSC_Perlmutter_epic': 'Perlmutter'}):
0058 return display_data({'daily': rows}, date(2026, 5, 1), date(2026, 6, 30), **options)
0059
0060 def test_ratio_uses_summed_hours_and_preserves_real_zero(self):
0061 result = self.data([
0062 ['2026-05-01', 'grex', False, '', 90, 100, 10, 0, 0, 0, 0, 0],
0063 ['2026-05-02', 'grex', False, '', 0, 900, 30, 0, 0, 0, 0, 0],
0064 ['2026-06-02', 'grex', False, '', 0, 60, 2, 0, 0, 0, 0, 0],
0065 ])
0066 may, june = result['cells']
0067 self.assertEqual(may['efficiency'], 9)
0068 self.assertEqual(may['jobs'], 40)
0069 self.assertEqual(june['efficiency'], 0)
0070
0071 def test_incident_exclusion_subtracts_hours_and_attempt_counts(self):
0072 row = ['2026-05-03', 'grex', False, '', 200, 1000, 120, 4, 100, 800, 100, 3]
0073 cell = self.data([row], exclude=True)['cells'][0]
0074 self.assertEqual((cell['finished'], cell['ended'], cell['jobs'], cell['missing']),
0075 (100, 200, 20, 1))
0076 self.assertEqual(cell['efficiency'], 50)
0077
0078 def test_unknown_execution_site_and_low_volume_are_visible(self):
0079 cell = self.data([
0080 ['2026-05-04', 'BNL_OSG_EPIC_PROD_1', True, '', 20, 40, 5, 1, 0, 0, 0, 0],
0081 ])
0082 self.assertIn('unresolved', cell['sites'][0]['label'].lower())
0083 self.assertIsNone(cell['cells'][0]['efficiency'])
0084 self.assertEqual(cell['cells'][0]['missing'], 1)
0085
0086 def test_weeks_start_monday_and_same_osg_site_combines_queues(self):
0087 rows = [
0088 ['2026-05-04', 'BNL_OSG_EPIC_PROD_1', True, 'site:ComputeCanada-Fir',
0089 70, 100, 2, 0, 0, 0, 0, 0],
0090 ['2026-05-10', 'BNL_OSG_PanDA_1', True, 'site:ComputeCanada-Fir',
0091 30, 100, 2, 0, 0, 0, 0, 0],
0092 ]
0093 result = self.data(rows, bucket='week')
0094 self.assertEqual(len(result['sites']), 1)
0095 self.assertEqual(result['sites'][0]['label'], 'OSG ยท Fir')
0096 self.assertEqual(result['cells'][0]['period'], '2026-05-04')
0097 self.assertEqual(result['cells'][0]['efficiency'], 50)
0098
0099 def test_historical_perlmutter_queues_form_one_site(self):
0100 result = self.data([
0101 ['2026-06-01', 'NERSC_Perlmutter_epic', False, '', 10, 100, 10, 0, 0, 0, 0, 0],
0102 ['2026-06-02', 'NERSC_Perlmutter_epic_test', False, '', 900, 1000, 50, 0, 0, 0, 0, 0],
0103 ])
0104 self.assertEqual(result['sites'], [{'id': 'queue:Perlmutter', 'label': 'Perlmutter'}])
0105 self.assertAlmostEqual(result['cells'][0]['efficiency'], 100 * 910 / 1100)
0106
0107 def test_refresh_removes_obsolete_recent_rows_but_retains_history(self):
0108 old = [['2026-05-01', 'historic'], ['2026-06-01', 'removed']]
0109 recent = [['2026-06-02', 'new']]
0110 self.assertEqual(merge_days(old, recent, date(2026, 6, 1)), [old[0], recent[0]])
0111 self.assertEqual(merge_days(old, recent, date(2026, 5, 1)), recent)
0112
0113 def test_full_reconciliation_retains_expired_archive_periods(self):
0114 old = ['2026-05-01', 'historic']
0115 recent = ['2026-06-02', 'new']
0116 with patch('monitor_app.compute_efficiency.CachedProduct') as store, \
0117 patch('monitor_app.compute_efficiency.query_days', return_value=[recent]):
0118 store.objects.filter.return_value.first.return_value.value = {'daily': [old]}
0119 self.assertEqual(build(full=True)['daily'], [old, recent])
0120
0121 def test_empty_full_source_cannot_erase_stored_history(self):
0122 with patch('monitor_app.compute_efficiency.CachedProduct') as store, \
0123 patch('monitor_app.compute_efficiency.query_days', return_value=[]):
0124 store.objects.filter.return_value.first.return_value.value = {'daily': [['2026-05-01']]}
0125 with self.assertRaisesRegex(RuntimeError, 'no source history'):
0126 build(full=True)