Skip to content

Commit

Permalink
Add Prometheus metrics to various parts of the bot (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaacG authored Aug 28, 2023
1 parent 6f6c93e commit c344560
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 3 deletions.
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install click discord.py python-dotenv
pip install prometheus_client
pip install requests sentry_sdk python-logging-loki aiohttp systemd-python
- name: Install linters
run: |
Expand Down
5 changes: 5 additions & 0 deletions cogs/inclusive_language.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import cast, Sequence

import discord
import prometheus_client # type: ignore
from discord.ext import commands

from cogs import base_cog
Expand Down Expand Up @@ -42,6 +43,9 @@ def __init__(
) -> None:
super().__init__(**kwargs)
self.patterns = patterns
self.prom_counter = prometheus_client.Counter(
"inclusive_language_triggered", "How many times inclusive language was triggered."
)

@commands.Cog.listener()
async def on_message(self, message: discord.Message) -> None:
Expand All @@ -54,6 +58,7 @@ async def on_message(self, message: discord.Message) -> None:
if not any(pattern.search(message.content) for pattern in self.patterns):
return
self.usage_stats[message.author.display_name].append(int(time.time()))
self.prom_counter.inc()
if channel.type == discord.ChannelType.public_thread:
await message.reply(MESSAGE, delete_after=DURATION, suppress_embeds=True)
elif channel.type == discord.ChannelType.text:
Expand Down
17 changes: 14 additions & 3 deletions cogs/mentor_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Sequence

import discord
import prometheus_client # type: ignore
from discord.ext import commands
from discord.ext import tasks
from exercism_lib import exercism
Expand All @@ -21,6 +22,12 @@
"get_theads": "SELECT track_slug, message_id FROM track_threads",
"add_thead": "INSERT INTO track_threads VALUES (:track_slug, :message_id)",
}
PROM_TRACK_COUNT = prometheus_client.Gauge("mentor_requests_tracks", "Number of tracks")
PROM_REQUEST_COUNT = prometheus_client.Gauge("mentor_requests", "Number of requests")
PROM_UPDATE_HIST = prometheus_client.Histogram("mentor_requests_update", "Update requests")
PROM_UPDATE_TRACK_HIST = prometheus_client.Histogram(
"mentor_requests_update_track", "Update one track", ["track"],
)


class RequestNotifier(base_cog.BaseCog):
Expand All @@ -45,10 +52,11 @@ def __init__(
self.lock = asyncio.Lock()

if tracks:
self.tracks = tracks
self.tracks = list(tracks)
else:
self.tracks = exercism.Exercism().all_tracks()
self.tracks.sort()
self.tracks.sort()
PROM_TRACK_COUNT.set(len(self.tracks))

self.synced_tracks: set[str] = set()
self.task_update_mentor_requests.start() # pylint: disable=E1101
Expand Down Expand Up @@ -105,6 +113,7 @@ async def update_track_requests(self, track: str) -> dict[str, str]:
self.conn.execute(QUERY["add_request"], data)
return requests

@PROM_UPDATE_HIST.time()
async def update_mentor_requests(self) -> None:
"""Update threads with new/expires requests."""
logger.debug("Start update_mentor_requests()")
Expand All @@ -114,7 +123,8 @@ async def update_mentor_requests(self) -> None:
for track in self.tracks:
try:
async with asyncio.timeout(30):
requests = await self.update_track_requests(track)
with PROM_UPDATE_TRACK_HIST.labels(track).time():
requests = await self.update_track_requests(track)
synced_tracks.add(track)
except asyncio.TimeoutError:
logging.warning("update_track_requests timed out for track %s.", track)
Expand Down Expand Up @@ -156,6 +166,7 @@ async def update_mentor_requests(self) -> None:
except discord.errors.NotFound:
logger.info("Message not found; dropping from DB. %s", message.jump_url)
await asyncio.sleep(0.1)
PROM_REQUEST_COUNT.set(len(self.requests))
logger.debug("End update_mentor_requests()")

@tasks.loop(minutes=10)
Expand Down
5 changes: 5 additions & 0 deletions cogs/mod_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
from discord import app_commands
from discord.ext import commands

import prometheus_client # type: ignore
from cogs import base_cog

logger = logging.getLogger(__name__)
PROM_MESSAGE_COUNTER = prometheus_client.Counter(
"mod_message_count", "Mod Message counter", ["message"],
)


class ModMessage(base_cog.BaseCog):
Expand Down Expand Up @@ -104,6 +108,7 @@ async def mod_message(
if mention:
content = f"{mention.mention} {content}"
self.usage_stats[message.value] += 1
PROM_MESSAGE_COUNTER.labels("message.value").inc()
await channel.send(content, suppress_embeds=True)

return mod_message
Expand Down
3 changes: 3 additions & 0 deletions cogs/track_react.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import re

import discord
import prometheus_client # type: ignore
from discord.ext import commands

from cogs import base_cog

logger = logging.getLogger(__name__)

URL_RE = re.compile(r"https?://\S+")
PROM_REACT_COUNTER = prometheus_client.Counter("react_count", "Reaction counter", ["emoji"])


class TrackReact(base_cog.BaseCog):
Expand Down Expand Up @@ -112,6 +114,7 @@ async def add_reacts(self, message: discord.Message, content: str) -> None:

for reaction in reactions:
self.usage_stats[reaction.name] += 1
PROM_REACT_COUNTER.labels(reaction.name).inc()
await message.add_reaction(reaction)
await asyncio.sleep(0.1)

Expand Down
6 changes: 6 additions & 0 deletions exercism_discord_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import discord
import dotenv
import logging_loki # type: ignore
import prometheus_client # type: ignore
import sentry_sdk # type: ignore
import systemd.journal # type: ignore
from discord.ext import commands
Expand Down Expand Up @@ -57,6 +58,7 @@ def __init__(
super().__init__(*args, **kwargs)
self.cogs_to_load = cogs_to_load
self.exercism_guild_id = exercism_guild_id
self.gauge_cogs_loaded = prometheus_client.Gauge("cogs_loaded", "Number of cogs running")

async def setup_hook(self):
"""Configure the bot with various Cogs."""
Expand All @@ -82,6 +84,7 @@ async def setup_hook(self):
logger.info("Loading cog %s", cog.__name__)
instance = cog(**combined)
await self.add_cog(instance, guild=guild)
self.gauge_cogs_loaded.inc()

async def on_error(self, event_method, /, *args, **kwargs) -> None:
"""Capture and log errors."""
Expand Down Expand Up @@ -211,6 +214,9 @@ def main(
if not has_setting("DISCORD_TOKEN"):
raise RuntimeError("Missing DISCORD_TOKEN")

if find_setting("PROMETHEUS_PORT"):
prometheus_client.start_http_server(int(find_setting("PROMETHEUS_PORT")))

# Start the bot.
intents = discord.Intents.default()
intents.members = True
Expand Down

0 comments on commit c344560

Please sign in to comment.