NTP-73461 | ANR Reporter | Color & Overall Metric (#16622)
This commit is contained in:
321
.github/actions/anr-reporter/report.py
vendored
321
.github/actions/anr-reporter/report.py
vendored
@@ -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):
|
||||
|
||||
2
.github/workflows/anr-reporter.yml
vendored
2
.github/workflows/anr-reporter.yml
vendored
@@ -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 }}
|
||||
|
||||
Reference in New Issue
Block a user