mirror of
https://gitlab.com/chicken-riders/RcGcDb.git
synced 2025-02-23 00:54:09 +00:00
Experimental support for time to send metric
This commit is contained in:
parent
b7deaeb1a7
commit
ad1cf8bf99
|
@ -13,6 +13,8 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with RcGcDw. If not, see <http://www.gnu.org/licenses/>.
|
# along with RcGcDw. If not, see <http://www.gnu.org/licenses/>.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
|
@ -24,19 +26,23 @@ from src.exceptions import EmbedListFull
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from wiki import Wiki
|
from wiki import Wiki
|
||||||
|
from domain import Domain
|
||||||
|
|
||||||
with open("src/api/template_settings.json", "r") as template_json:
|
with open("src/api/template_settings.json", "r") as template_json:
|
||||||
settings: dict = json.load(template_json)
|
settings: dict = json.load(template_json)
|
||||||
|
|
||||||
|
|
||||||
class DiscordMessageMetadata:
|
class DiscordMessageMetadata:
|
||||||
def __init__(self, method, log_id = None, page_id = None, rev_id = None, webhook_url = None, message_display = None):
|
def __init__(self, method, log_id = None, page_id = None, rev_id = None, webhook_url = None, message_display = None,
|
||||||
|
time_of_change: Optional[datetime.datetime] = None, domain: Domain = None):
|
||||||
self.method = method # unused, remains for compatibility reasons
|
self.method = method # unused, remains for compatibility reasons
|
||||||
self.page_id = page_id
|
self.page_id = page_id
|
||||||
self.log_id = log_id
|
self.log_id = log_id
|
||||||
self.rev_id = rev_id
|
self.rev_id = rev_id
|
||||||
self.webhook_url = webhook_url
|
self.webhook_url = webhook_url
|
||||||
self.message_display = message_display
|
self.message_display = message_display
|
||||||
|
self.time_of_change = time_of_change.replace(tzinfo=datetime.timezone.utc) # We are assuming all wikis use UTC as server time (this is terrible, we need to do this better)
|
||||||
|
self.domain = domain
|
||||||
|
|
||||||
def matches(self, other: dict):
|
def matches(self, other: dict):
|
||||||
for key, value in other.items():
|
for key, value in other.items():
|
||||||
|
@ -182,7 +188,7 @@ class StackedDiscordMessage():
|
||||||
message_structure["content"] = "\n".join([message.return_content() for message in self.message_list])
|
message_structure["content"] = "\n".join([message.return_content() for message in self.message_list])
|
||||||
elif self.message_type == 1:
|
elif self.message_type == 1:
|
||||||
message_structure["embeds"] = [message.embed for message in self.message_list]
|
message_structure["embeds"] = [message.embed for message in self.message_list]
|
||||||
if self.message_list and "components" in self.message_list[0].webhook_object:
|
if self.message_list and "components" in self.message_list[0].webhook_object: # use components from the first message
|
||||||
message_structure["components"] = self.message_list[0].webhook_object["components"]
|
message_structure["components"] = self.message_list[0].webhook_object["components"]
|
||||||
return json.dumps(message_structure)
|
return json.dumps(message_structure)
|
||||||
|
|
||||||
|
|
|
@ -163,6 +163,11 @@ class MessageQueue:
|
||||||
self.global_rate_limit = True
|
self.global_rate_limit = True
|
||||||
await asyncio.sleep(e.remaining / 1000)
|
await asyncio.sleep(e.remaining / 1000)
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
if status == 0:
|
||||||
|
for message in msg.message_list:
|
||||||
|
if message.metadata.domain is not None and message.metadata.time_of_change is not None:
|
||||||
|
message.metadata.domain.register_message_timing_report(message.metadata.time_of_change)
|
||||||
for queue_message in messages[max(index-len(msg.message_list), 0):index+1]: # mark messages as delivered
|
for queue_message in messages[max(index-len(msg.message_list), 0):index+1]: # mark messages as delivered
|
||||||
queue_message.confirm_sent_status(webhook_url)
|
queue_message.confirm_sent_status(webhook_url)
|
||||||
if client_error is False:
|
if client_error is False:
|
||||||
|
@ -204,7 +209,7 @@ async def handle_discord_http(code: int, formatted_embed: str, result: ClientRes
|
||||||
# TODO remove_webhook_maybe()
|
# TODO remove_webhook_maybe()
|
||||||
raise aiohttp.ClientError("Message sent to bad webhook.")
|
raise aiohttp.ClientError("Message sent to bad webhook.")
|
||||||
else:
|
else:
|
||||||
return 0
|
return 1
|
||||||
elif code == 429:
|
elif code == 429:
|
||||||
logger.error("We are sending too many requests to the Discord, slowing down...")
|
logger.error("We are sending too many requests to the Discord, slowing down...")
|
||||||
if "x-ratelimit-global" in result.headers.keys():
|
if "x-ratelimit-global" in result.headers.keys():
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -10,6 +11,7 @@ import sys
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
|
from misc import LimitedList
|
||||||
from src.discord.message import DiscordMessage
|
from src.discord.message import DiscordMessage
|
||||||
from src.config import settings
|
from src.config import settings
|
||||||
from src.argparser import command_line_args
|
from src.argparser import command_line_args
|
||||||
|
@ -30,16 +32,23 @@ class Domain:
|
||||||
self.wikis: OrderedDict[str, src.wiki.Wiki] = OrderedDict()
|
self.wikis: OrderedDict[str, src.wiki.Wiki] = OrderedDict()
|
||||||
self.irc: Optional[src.irc_feed.AioIRCCat] = None
|
self.irc: Optional[src.irc_feed.AioIRCCat] = None
|
||||||
self.last_failure_report = 0
|
self.last_failure_report = 0
|
||||||
self.failure_data = [0, 0, 0] # Amount of failures, timestamp of last failure info, count of announcements
|
self.message_timings: LimitedList = LimitedList(limit=100)
|
||||||
# self.discussions_handler: Optional[Discussions] = Discussions(self.wikis) if name == "fandom.com" else None
|
# self.discussions_handler: Optional[Discussions] = Discussions(self.wikis) if name == "fandom.com" else None
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self.wikis)
|
return iter(self.wikis)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
if len(self.message_timings) > 0: # min throws exception when used on empty iterable
|
||||||
|
tmin, avg, tmax = (self.convert_seconds_to_readable(min(self.message_timings)),
|
||||||
|
self.convert_seconds_to_readable(int(sum(self.message_timings)/len(self.message_timings))),
|
||||||
|
self.convert_seconds_to_readable(max(self.message_timings)))
|
||||||
|
else:
|
||||||
|
tmin, avg, tmax = 0, 0, 0
|
||||||
return (f"<Domain name='{self.name}' task='{self.task}' wikis='{self.wikis}' "
|
return (f"<Domain name='{self.name}' task='{self.task}' wikis='{self.wikis}' "
|
||||||
f"irc='{self.irc.connection.connected if self.irc else False}' "
|
f"irc='{self.irc.connection.connected if self.irc else False}' "
|
||||||
f"calculated_delay={self.calculate_sleep_time(len(self)) if not self.irc else 'handled by IRC scheduler'}>")
|
f"calculated_delay={self.calculate_sleep_time(len(self)) if not self.irc else 'handled by IRC scheduler'} "
|
||||||
|
f"msgdelays=(min={tmin}, avg={avg}, max={tmax})>")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
@ -50,6 +59,11 @@ class Domain:
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.wikis)
|
return len(self.wikis)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_seconds_to_readable(seconds: int) -> str:
|
||||||
|
"""Helper function to prepare human readable times for domain report"""
|
||||||
|
return f"{int(seconds/60)}m{seconds % 60}s"
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
"""Destroy the domain – do all of the tasks that should make sure there is no leftovers before being collected by GC"""
|
"""Destroy the domain – do all of the tasks that should make sure there is no leftovers before being collected by GC"""
|
||||||
if self.irc:
|
if self.irc:
|
||||||
|
@ -129,6 +143,13 @@ class Domain:
|
||||||
if affected:
|
if affected:
|
||||||
return affected
|
return affected
|
||||||
|
|
||||||
|
def register_message_timing_report(self, initial_time: datetime.datetime, send_time: Optional[datetime.datetime] = None) -> None:
|
||||||
|
"""This function registers time between edit being made and message with given edit being sent on Discord
|
||||||
|
For metrics and debugging"""
|
||||||
|
if send_time is None:
|
||||||
|
send_time = datetime.datetime.now(tz=datetime.timezone.utc)
|
||||||
|
self.message_timings.append((send_time - initial_time).seconds)
|
||||||
|
|
||||||
async def irc_scheduler(self):
|
async def irc_scheduler(self):
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
|
|
15
src/misc.py
15
src/misc.py
|
@ -12,7 +12,6 @@ from src.config import settings
|
||||||
|
|
||||||
logger = logging.getLogger("rcgcdw.misc")
|
logger = logging.getLogger("rcgcdw.misc")
|
||||||
|
|
||||||
|
|
||||||
def get_paths(wiki: str, request) -> tuple:
|
def get_paths(wiki: str, request) -> tuple:
|
||||||
"""Prepares wiki paths for the functions"""
|
"""Prepares wiki paths for the functions"""
|
||||||
parsed_url = urlparse(wiki)
|
parsed_url = urlparse(wiki)
|
||||||
|
@ -210,3 +209,17 @@ def prepare_settings(display_mode: int) -> dict:
|
||||||
template["appearance"]["embed"]["embed_images"] = True if display_mode > 1 else False
|
template["appearance"]["embed"]["embed_images"] = True if display_mode > 1 else False
|
||||||
template["appearance"]["embed"]["show_edit_changes"] = True if display_mode > 2 else False
|
template["appearance"]["embed"]["show_edit_changes"] = True if display_mode > 2 else False
|
||||||
return template
|
return template
|
||||||
|
|
||||||
|
|
||||||
|
class LimitedList(list):
|
||||||
|
def __init__(self, *args, limit=settings.get("queue_limit", 30)):
|
||||||
|
list.__init__(self, *args)
|
||||||
|
self.queue_limit = limit
|
||||||
|
|
||||||
|
def append(self, obj) -> None:
|
||||||
|
if len(self) > self.queue_limit:
|
||||||
|
self.pop(0)
|
||||||
|
super(LimitedList, self).append(obj)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "\n".join([str(x) for x in self])
|
||||||
|
|
|
@ -2,6 +2,7 @@ import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import aiohttp.web_request
|
import aiohttp.web_request
|
||||||
|
|
||||||
|
from misc import LimitedList
|
||||||
from src.config import settings
|
from src.config import settings
|
||||||
from typing import Union, Optional, List
|
from typing import Union, Optional, List
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
@ -15,9 +16,6 @@ class LogType(Enum):
|
||||||
SCAN_REASON = 5
|
SCAN_REASON = 5
|
||||||
|
|
||||||
|
|
||||||
queue_limit = settings.get("queue_limit", 30)
|
|
||||||
|
|
||||||
|
|
||||||
class Log:
|
class Log:
|
||||||
"""Log class represents an event that happened to a wiki fetch. Main purpose of those logs is debug and error-tracking."""
|
"""Log class represents an event that happened to a wiki fetch. Main purpose of those logs is debug and error-tracking."""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
@ -33,19 +31,6 @@ class Log:
|
||||||
return f"<Log {self.type.name} at {datetime.fromtimestamp(float(self.time)).isoformat()} on {self.title} with details: {self.details}>"
|
return f"<Log {self.type.name} at {datetime.fromtimestamp(float(self.time)).isoformat()} on {self.title} with details: {self.details}>"
|
||||||
|
|
||||||
|
|
||||||
class LimitedList(list):
|
|
||||||
def __init__(self, *args):
|
|
||||||
list.__init__(self, *args)
|
|
||||||
|
|
||||||
def append(self, obj: Log) -> None:
|
|
||||||
if len(self) > queue_limit:
|
|
||||||
self.pop(0)
|
|
||||||
super(LimitedList, self).append(obj)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "\n".join([str(x) for x in self])
|
|
||||||
|
|
||||||
|
|
||||||
class Statistics:
|
class Statistics:
|
||||||
def __init__(self, rc_id: Optional[int], discussion_id: Optional[str]):
|
def __init__(self, rc_id: Optional[int], discussion_id: Optional[str]):
|
||||||
self.last_request: Optional[aiohttp.web_request.Request] = None
|
self.last_request: Optional[aiohttp.web_request.Request] = None
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
|
@ -460,7 +461,9 @@ async def rc_processor(wiki: Wiki, change: dict, changed_categories: dict, displ
|
||||||
from src.misc import LinkParser
|
from src.misc import LinkParser
|
||||||
LinkParser = LinkParser(wiki.client.WIKI_JUST_DOMAIN)
|
LinkParser = LinkParser(wiki.client.WIKI_JUST_DOMAIN)
|
||||||
metadata = DiscordMessageMetadata("POST", rev_id=change.get("revid", None), log_id=change.get("logid", None),
|
metadata = DiscordMessageMetadata("POST", rev_id=change.get("revid", None), log_id=change.get("logid", None),
|
||||||
page_id=change.get("pageid", None), message_display=display_options.display)
|
page_id=change.get("pageid", None), message_display=display_options.display,
|
||||||
|
time_of_change=datetime.datetime.strptime(change["timestamp"], '%Y-%m-%dT%H:%M:%SZ'),
|
||||||
|
domain=wiki.domain)
|
||||||
context = Context("embed" if display_options.display > 0 else "compact", "recentchanges", webhooks, wiki.client,
|
context = Context("embed" if display_options.display > 0 else "compact", "recentchanges", webhooks, wiki.client,
|
||||||
langs[display_options.lang]["formatters"], prepare_settings(display_options.display), display_options.buttons)
|
langs[display_options.lang]["formatters"], prepare_settings(display_options.display), display_options.buttons)
|
||||||
if ("actionhidden" in change or "suppressed" in change) and "suppressed" not in settings["ignored"]: # if event is hidden using suppression
|
if ("actionhidden" in change or "suppressed" in change) and "suppressed" not in settings["ignored"]: # if event is hidden using suppression
|
||||||
|
|
Loading…
Reference in a new issue