From aeee80022bd11e11239826fe442b9d633958a91a Mon Sep 17 00:00:00 2001 From: Shivam Goyal Date: Tue, 17 Jun 2025 17:29:27 +0530 Subject: [PATCH] NTP-73461 | ANR Reporter | Color & Overall Metric (#16622) --- .github/actions/anr-reporter/report.py | 321 +++++++++++++++++++------ .github/workflows/anr-reporter.yml | 2 +- 2 files changed, 251 insertions(+), 72 deletions(-) diff --git a/.github/actions/anr-reporter/report.py b/.github/actions/anr-reporter/report.py index 9cad43a234..ac952a2e8d 100644 --- a/.github/actions/anr-reporter/report.py +++ b/.github/actions/anr-reporter/report.py @@ -12,7 +12,7 @@ class ANRDataCollector: def __init__(self): self.access_token = os.getenv('GOOGLE_ACCESS_TOKEN') self.slack_bot_token = os.getenv('SLACK_BOT_TOKEN') - self.slack_channel_id = os.getenv('SLACK_CHANNEL_ID', 'C1234567890') + self.slack_channel_id = os.getenv('SLACK_CHANNEL_ID') self.package_name = 'com.naviapp' self.api_base_url = 'https://playdeveloperreporting.googleapis.com/v1beta1' @@ -43,6 +43,27 @@ class ANRDataCollector: "pageSize": 100000 } + def _build_overall_query_payload(self, start_date, end_date): + return { + "timelineSpec": { + "aggregationPeriod": "DAILY", + "startTime": { + "timeZone": {"id": "America/Los_Angeles"}, + "month": start_date.month, + "day": start_date.day, + "year": start_date.year + }, + "endTime": { + "timeZone": {"id": "America/Los_Angeles"}, + "month": end_date.month, + "day": end_date.day, + "year": end_date.year + } + }, + "metrics": ["userPerceivedAnrRate", "userPerceivedAnrRate28dUserWeighted"], + "pageSize": 100000 + } + def _parse_anr_pivot_response(self, response_data, metric_name="userPerceivedAnrRate"): if not response_data.get('rows'): return { @@ -87,7 +108,7 @@ class ANRDataCollector: all_versions.add(version_code) sorted_dates = sorted(list(all_dates), reverse=True)[:10] - sorted_versions = sorted(list(all_versions), key=lambda x: int(x) if x.isdigit() else 0, reverse=True)[:8] + sorted_versions = sorted(list(all_versions), key=lambda x: int(x) if x.isdigit() else 0, reverse=True)[:10] return { 'pivot_data': pivot_data, @@ -95,12 +116,48 @@ class ANRDataCollector: 'versions': sorted_versions } - def _create_anr_table_image(self, pivot_result, title): + def _parse_overall_anr_response(self, response_data, metric_name="userPerceivedAnrRate"): + if not response_data.get('rows'): + return {'pivot_data': {}, 'dates': []} + + pivot_data = {} + all_dates = set() + + for row in response_data['rows']: + date_str = None + anr_rate = 0.0 + + start_time = row.get('startTime', {}) + if start_time: + year = start_time.get('year') + month = start_time.get('month') + day = start_time.get('day') + if year and month and day: + date_str = f"{year}-{month:02d}-{day:02d}" + + for metric in row.get('metrics', []): + if metric.get('metric') == metric_name: + decimal_value = metric.get('decimalValue', {}) + anr_rate = float(decimal_value.get('value', '0.0')) + break + + if date_str: + pivot_data[date_str] = anr_rate + all_dates.add(date_str) + + sorted_dates = sorted(list(all_dates), reverse=True)[:10] + + return { + 'pivot_data': pivot_data, + 'dates': sorted_dates + } + + def _create_anr_table_image(self, pivot_result, title, overall_data=None): dates = pivot_result['dates'] - versions = pivot_result['versions'] + versions = pivot_result.get('versions', []) pivot_data = pivot_result['pivot_data'] - if not dates or not versions: + if not dates and not versions: fig, ax = plt.subplots(figsize=(10, 6)) ax.text(0.5, 0.5, 'No ANR data available', ha='center', va='center', fontsize=16, color='gray') @@ -115,80 +172,184 @@ class ANRDataCollector: plt.close() return buffer - table_data = [] - - header = ['Date'] + [f'v{v}' for v in versions] - table_data.append(header) - - for date in dates: - row = [datetime.strptime(date, '%Y-%m-%d').strftime('%d %b')] - for version in versions: - if date in pivot_data and version in pivot_data[date]: - rate = pivot_data[date][version] * 100 + if versions: + table_data = [] + + if overall_data: + header = ['Date', 'Overall'] + [f'v{v}' for v in versions] + else: + header = ['Date'] + [f'v{v}' for v in versions] + table_data.append(header) + + for date in dates: + row = [datetime.strptime(date, '%Y-%m-%d').strftime('%d %b')] + + if overall_data: + if date in overall_data['pivot_data']: + rate = overall_data['pivot_data'][date] * 100 + if rate == 0.0: + row.append('-') + else: + row.append(f'{rate:.2f}%') + else: + row.append('-') + + for version in versions: + if date in pivot_data and version in pivot_data[date]: + rate = pivot_data[date][version] * 100 + if rate == 0.0: + row.append('-') + else: + row.append(f'{rate:.2f}%') + else: + row.append('-') + table_data.append(row) + + num_cols = len(header) + num_rows = len(dates) + 1 + fig_width = max(8, num_cols * 1.2) + fig_height = max(6, num_rows * 0.6 + 2) + + fig, ax = plt.subplots(figsize=(fig_width, fig_height)) + ax.axis('tight') + ax.axis('off') + + table = ax.table(cellText=table_data[1:], + colLabels=table_data[0], + cellLoc='center', + loc='center', + bbox=[0, 0, 1, 1]) + + table.auto_set_font_size(False) + table.set_fontsize(10) + table.scale(1, 2) + + for i in range(num_cols): + cell = table[(0, i)] + cell.set_facecolor('#4472C4') + cell.set_text_props(weight='bold', color='white') + cell.set_height(0.08) + + for i in range(1, num_rows): + for j in range(num_cols): + cell = table[(i, j)] + cell.set_height(0.06) + + if j == 0: + cell.set_facecolor('#F2F2F2') + cell.set_text_props(weight='bold') + else: + cell_text = table_data[i][j] + if cell_text == '-': + cell.set_facecolor('#FFFFFF') + cell.set_text_props(color='#999999') + else: + if overall_data and j == 1: + try: + rate_value = float(cell_text.replace('%', '')) + if rate_value > 0.47: + cell.set_facecolor('#FFC0C0') + else: + cell.set_facecolor('#FFFFFF') + except (ValueError, AttributeError): + cell.set_facecolor('#FFFFFF') + else: + cell.set_facecolor('#FFFFFF') + + cell.set_edgecolor('#000000') + cell.set_linewidth(1) + + buffer = BytesIO() + plt.savefig(buffer, format='png', dpi=200, bbox_inches='tight', + facecolor='white', edgecolor='none', pad_inches=0.3) + buffer.seek(0) + plt.close() + + return buffer + else: + table_data = [] + + header = ['Date', 'ANR Rate'] + table_data.append(header) + + for date in dates: + row = [datetime.strptime(date, '%Y-%m-%d').strftime('%d %b')] + if date in pivot_data: + rate = pivot_data[date] * 100 if rate == 0.0: row.append('-') else: row.append(f'{rate:.2f}%') else: row.append('-') - table_data.append(row) - - num_cols = len(versions) + 1 - num_rows = len(dates) + 1 - fig_width = max(8, num_cols * 1.2) - fig_height = max(6, num_rows * 0.6 + 2) - - fig, ax = plt.subplots(figsize=(fig_width, fig_height)) - ax.axis('tight') - ax.axis('off') - - table = ax.table(cellText=table_data[1:], - colLabels=table_data[0], - cellLoc='center', - loc='center', - bbox=[0, 0, 1, 1]) - - table.auto_set_font_size(False) - table.set_fontsize(10) - table.scale(1, 2) - - for i in range(num_cols): - cell = table[(0, i)] - cell.set_facecolor('#4472C4') - cell.set_text_props(weight='bold', color='white') - cell.set_height(0.08) - - for i in range(1, num_rows): - for j in range(num_cols): - cell = table[(i, j)] - cell.set_height(0.06) - - if j == 0: - cell.set_facecolor('#F2F2F2') - cell.set_text_props(weight='bold') - else: - cell.set_facecolor('#FFFFFF') - cell_text = table_data[i][j] - if cell_text == '-': - cell.set_text_props(color='#999999') - - cell.set_edgecolor('#000000') - cell.set_linewidth(1) - - buffer = BytesIO() - plt.savefig(buffer, format='png', dpi=200, bbox_inches='tight', - facecolor='white', edgecolor='none', pad_inches=0.3) - buffer.seek(0) - plt.close() - - return buffer + table_data.append(row) + + num_cols = 2 + num_rows = len(dates) + 1 + fig_width = max(8, num_cols * 1.2) + fig_height = max(6, num_rows * 0.6 + 2) + + fig, ax = plt.subplots(figsize=(fig_width, fig_height)) + ax.axis('tight') + ax.axis('off') + + table = ax.table(cellText=table_data[1:], + colLabels=table_data[0], + cellLoc='center', + loc='center', + bbox=[0, 0, 1, 1]) + + table.auto_set_font_size(False) + table.set_fontsize(10) + table.scale(1, 2) + + for i in range(num_cols): + cell = table[(0, i)] + cell.set_facecolor('#4472C4') + cell.set_text_props(weight='bold', color='white') + cell.set_height(0.08) + + for i in range(1, num_rows): + for j in range(num_cols): + cell = table[(i, j)] + cell.set_height(0.06) + + if j == 0: + cell.set_facecolor('#F2F2F2') + cell.set_text_props(weight='bold') + else: + cell_text = table_data[i][j] + if cell_text == '-': + cell.set_facecolor('#FFFFFF') + cell.set_text_props(color='#999999') + else: + try: + rate_value = float(cell_text.replace('%', '')) + if rate_value > 0.47: + cell.set_facecolor('#FFC0C0') + else: + cell.set_facecolor('#FFFFFF') + except (ValueError, AttributeError): + cell.set_facecolor('#FFFFFF') + + cell.set_edgecolor('#000000') + cell.set_linewidth(1) + + buffer = BytesIO() + plt.savefig(buffer, format='png', dpi=200, bbox_inches='tight', + facecolor='white', edgecolor='none', pad_inches=0.3) + buffer.seek(0) + plt.close() + + return buffer def format_slack_messages(self, anr_data): images = [] regular_image = self._create_anr_table_image( anr_data['regular_pivot'], - "User Perceived ANR Rate - Daily" + "User Perceived ANR Rate - Daily", + overall_data=anr_data['overall_regular'] ) images.append({ 'buffer': regular_image, @@ -198,7 +359,8 @@ class ANRDataCollector: rolling_image = self._create_anr_table_image( anr_data['rolling_pivot'], - "User Perceived ANR Rate - 28 Days Rolling" + "User Perceived ANR Rate - 28 Days Rolling", + overall_data=anr_data['overall_rolling'] ) images.append({ 'buffer': rolling_image, @@ -292,7 +454,7 @@ class ANRDataCollector: def collect_anr_data(self, days_back=10): try: - end_date = datetime.now(pytz.timezone('America/Los_Angeles')) + end_date = datetime.now(pytz.timezone('Asia/Kolkata')) start_date = end_date - timedelta(days=days_back) url = f"{self.api_base_url}/apps/{self.package_name}/anrRateMetricSet:query" @@ -303,7 +465,7 @@ class ANRDataCollector: payload = self._build_query_payload(start_date, end_date) - print(f"🔍 Querying ANR data from {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}") + print(f"🔍 Querying ANR data with versions: {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}") response = requests.post(url, json=payload, headers=headers, timeout=30) @@ -315,11 +477,26 @@ class ANRDataCollector: regular_pivot = self._parse_anr_pivot_response(response_data, "userPerceivedAnrRate") rolling_pivot = self._parse_anr_pivot_response(response_data, "userPerceivedAnrRate28dUserWeighted") + print(f"🔍 Querying ANR data without versions: {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}") + overall_payload = self._build_overall_query_payload(start_date, end_date) + overall_response = requests.post(url, json=overall_payload, headers=headers, timeout=30) + + if overall_response.status_code != 200: + print(f"⚠️ Overall data API request failed: {overall_response.status_code} - {overall_response.text}") + overall_regular = {'pivot_data': {}, 'dates': []} + overall_rolling = {'pivot_data': {}, 'dates': []} + else: + overall_response_data = overall_response.json() + overall_regular = self._parse_overall_anr_response(overall_response_data, "userPerceivedAnrRate") + overall_rolling = self._parse_overall_anr_response(overall_response_data, "userPerceivedAnrRate28dUserWeighted") + print(f"✅ ANR data collected successfully") return { 'regular_pivot': regular_pivot, 'rolling_pivot': rolling_pivot, + 'overall_regular': overall_regular, + 'overall_rolling': overall_rolling, 'status': 'success' } @@ -329,7 +506,9 @@ class ANRDataCollector: return { 'status': 'error', 'regular_pivot': {'pivot_data': {}, 'dates': [], 'versions': []}, - 'rolling_pivot': {'pivot_data': {}, 'dates': [], 'versions': []} + 'rolling_pivot': {'pivot_data': {}, 'dates': [], 'versions': []}, + 'overall_regular': {'pivot_data': {}, 'dates': []}, + 'overall_rolling': {'pivot_data': {}, 'dates': []} } def run(self): diff --git a/.github/workflows/anr-reporter.yml b/.github/workflows/anr-reporter.yml index 4661fea717..c0d7c38303 100644 --- a/.github/workflows/anr-reporter.yml +++ b/.github/workflows/anr-reporter.yml @@ -3,7 +3,7 @@ name: ANR Reporter CI on: workflow_dispatch: schedule: - - cron: '30 8 * * *' + - cron: '30 12 * * *' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}