From 4ea02e0274db3f92164a8990f8432a03f4309415 Mon Sep 17 00:00:00 2001 From: Isaac Milarsky Date: Wed, 16 Oct 2024 16:41:45 -0500 Subject: [PATCH 1/3] patch BaseMetric to handle ratelimits Signed-off-by: Isaac Milarsky --- scripts/metricsLib/metrics_data_structures.py | 61 +++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/scripts/metricsLib/metrics_data_structures.py b/scripts/metricsLib/metrics_data_structures.py index 1e71fa592b..e5d2c53922 100644 --- a/scripts/metricsLib/metrics_data_structures.py +++ b/scripts/metricsLib/metrics_data_structures.py @@ -4,6 +4,7 @@ import json from json.decoder import JSONDecodeError import datetime +from time import sleep, mktime, gmtime, time, localtime from functools import reduce import operator import requests @@ -75,25 +76,49 @@ def hit_metric(self, params=None): endpoint_to_hit = self.url.format(**params) request_params = None - if self.headers: - _args_ = (self.method, endpoint_to_hit) - _kwargs_ = { - "params": request_params, - "headers": self.headers, - "timeout": TIMEOUT_IN_SECONDS - } - response = requests.request(*_args_, **_kwargs_) - else: - response = requests.request( - self.method, endpoint_to_hit, params=request_params, timeout=TIMEOUT_IN_SECONDS) - - try: - if response.status_code == 200: - response_json = json.loads(response.text) + attempts = 0 + + while attempts < 10: + if self.headers: + _args_ = (self.method, endpoint_to_hit) + _kwargs_ = { + "params": request_params, + "headers": self.headers, + "timeout": TIMEOUT_IN_SECONDS + } + response = requests.request(*_args_, **_kwargs_) else: - raise ConnectionError(f"Non valid status code {response.status_code}!") - except JSONDecodeError: - response_json = {} + response = requests.request( + self.method, endpoint_to_hit, params=request_params, timeout=TIMEOUT_IN_SECONDS) + + try: + if response.status_code == 200: + response_json = json.loads(response.text) + break + + #check for rate limit response + if response.status_code in (403,429): + #rate limit was triggered. + wait_until = int(response.headers.get("x-ratelimit-reset")) + wait_in_seconds = int( + mktime(gmtime(wait_until)) - + mktime(gmtime(time())) + ) + wait_until_time = localtime(wait_until) + + print(f"Ran into rate limit sleeping for {self.name}!") + print( + f"sleeping until {wait_until_time.tm_hour}:{wait_until_time.tm_min} ({wait_in_seconds} seconds)" + ) + sleep(wait_in_seconds) + + response_json = {} + attempts += 1 + else: + raise ConnectionError(f"Non valid status code {response.status_code}!") + except JSONDecodeError: + response_json = {} + attempts += 1 return response_json From 5e4900606a22b64d85239b753390b3408416cbe5 Mon Sep 17 00:00:00 2001 From: Isaac Milarsky Date: Wed, 16 Oct 2024 16:44:50 -0500 Subject: [PATCH 2/3] change else to elif Signed-off-by: Isaac Milarsky --- scripts/metricsLib/metrics_data_structures.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/metricsLib/metrics_data_structures.py b/scripts/metricsLib/metrics_data_structures.py index e5d2c53922..01c15163e9 100644 --- a/scripts/metricsLib/metrics_data_structures.py +++ b/scripts/metricsLib/metrics_data_structures.py @@ -95,9 +95,7 @@ def hit_metric(self, params=None): if response.status_code == 200: response_json = json.loads(response.text) break - - #check for rate limit response - if response.status_code in (403,429): + elif response.status_code in (403,429): #rate limit was triggered. wait_until = int(response.headers.get("x-ratelimit-reset")) wait_in_seconds = int( From cd32c41cae52f6e61bcf5e32e641829fa951102a Mon Sep 17 00:00:00 2001 From: Isaac Milarsky Date: Wed, 16 Oct 2024 16:54:57 -0500 Subject: [PATCH 3/3] add retries as a constant in constants.py and raise a connectionError if rate limit can't be slept to fix Signed-off-by: Isaac Milarsky --- scripts/metricsLib/constants.py | 1 + scripts/metricsLib/metrics_data_structures.py | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/metricsLib/constants.py b/scripts/metricsLib/constants.py index 32430d8012..af1a71ab20 100644 --- a/scripts/metricsLib/constants.py +++ b/scripts/metricsLib/constants.py @@ -7,6 +7,7 @@ from enum import Enum TIMEOUT_IN_SECONDS = 120 +REQUEST_RETRIES = 5 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) # Folder Names to send over our projects tracked data PATH_TO_METRICS_DATA = (Path(__file__).parent / diff --git a/scripts/metricsLib/metrics_data_structures.py b/scripts/metricsLib/metrics_data_structures.py index 01c15163e9..c2d67fcbb9 100644 --- a/scripts/metricsLib/metrics_data_structures.py +++ b/scripts/metricsLib/metrics_data_structures.py @@ -8,7 +8,7 @@ from functools import reduce import operator import requests -from metricsLib.constants import TIMEOUT_IN_SECONDS, GH_GQL_ENDPOINT +from metricsLib.constants import TIMEOUT_IN_SECONDS, GH_GQL_ENDPOINT, REQUEST_RETRIES # Simple metric that can be represented by a count or value. @@ -78,7 +78,7 @@ def hit_metric(self, params=None): attempts = 0 - while attempts < 10: + while attempts < REQUEST_RETRIES: if self.headers: _args_ = (self.method, endpoint_to_hit) _kwargs_ = { @@ -112,6 +112,11 @@ def hit_metric(self, params=None): response_json = {} attempts += 1 + + if attempts >= REQUEST_RETRIES: + raise ConnectionError( + f"Rate limit was reached and couldn't be rectified after {attempts} tries" + ) else: raise ConnectionError(f"Non valid status code {response.status_code}!") except JSONDecodeError: