Skip to content

Commit 66ea94f

Browse files
authored
Add datadog stats to GB sync (#43)
Signed-off-by: Phil Dibowitz <phil@ipom.com>
1 parent 506d209 commit 66ea94f

File tree

3 files changed

+132
-2
lines changed

3 files changed

+132
-2
lines changed

.github/workflows/sync.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ jobs:
2626
env:
2727
GUIDEBOOK_API_TOKEN: ${{ secrets.GUIDEBOOK_API_TOKEN }}
2828
GUIDEBOOK_JWT_TOKEN: ${{ secrets.GUIDEBOOK_JWT_TOKEN }}
29+
DD_API_KEY: ${{ secrets.DATADOG_API_KEY }}
2930
run: |
3031
CMD="python ./guidebook/sync_guidebook.py --max-deletes 5"
3132

guidebook/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pytz
44
xdg-base-dirs
55
dateutils
66
markdownify
7+
datadog-api-client

guidebook/sync_guidebook.py

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@
3333
from datetime import datetime
3434
from dateutil import parser
3535
from markdownify import markdownify as md
36+
from datadog_api_client import ApiClient, Configuration
37+
from datadog_api_client.v2.api.metrics_api import MetricsApi
38+
from datadog_api_client.v2.model.metric_intake_type import MetricIntakeType
39+
from datadog_api_client.v2.model.metric_payload import MetricPayload
40+
from datadog_api_client.v2.model.metric_point import MetricPoint
41+
from datadog_api_client.v2.model.metric_resource import MetricResource
42+
from datadog_api_client.v2.model.metric_series import MetricSeries
3643
import click
3744
import json
3845
import logging
@@ -41,6 +48,7 @@
4148
import re
4249
import requests
4350
import sys
51+
import time
4452

4553
try:
4654
import xdg_base_dirs as xdg
@@ -51,6 +59,98 @@
5159
GUIDE_NAME = "SCaLE 23x"
5260

5361

62+
class StatsTracker:
63+
"""Track statistics for additions, updates, and deletions of items."""
64+
65+
def __init__(self):
66+
self.stats = {
67+
"tracks": {"added": 0, "updated": 0, "deleted": 0},
68+
"rooms": {"added": 0, "updated": 0, "deleted": 0},
69+
"sessions": {"added": 0, "updated": 0, "deleted": 0},
70+
"map_regions": {"added": 0, "updated": 0, "deleted": 0},
71+
}
72+
73+
def increment(self, item_type, operation):
74+
"""Increment counter for a given item type and operation."""
75+
if item_type in self.stats and operation in self.stats[item_type]:
76+
self.stats[item_type][operation] += 1
77+
78+
def get_stats(self):
79+
"""Return the current statistics."""
80+
return self.stats
81+
82+
def log_stats(self, logger):
83+
"""Log statistics summary."""
84+
logger.info("=" * 60)
85+
logger.info("SYNC STATISTICS SUMMARY")
86+
logger.info("=" * 60)
87+
for item_type, operations in self.stats.items():
88+
total = sum(operations.values())
89+
if total > 0:
90+
logger.info(
91+
f"{item_type.upper()}: "
92+
f"Added={operations['added']}, "
93+
f"Updated={operations['updated']}, "
94+
f"Deleted={operations['deleted']}"
95+
)
96+
logger.info("=" * 60)
97+
98+
def send_to_datadog(self, logger, dryrun=False):
99+
"""Send metrics to Datadog."""
100+
if dryrun:
101+
logger.info("[DRYRUN] Would have sent metrics to Datadog")
102+
return
103+
104+
dd_api_key = os.getenv("DD_API_KEY")
105+
dd_site = os.getenv("DD_SITE", "datadoghq.com")
106+
107+
if not dd_api_key:
108+
logger.warning(
109+
"DD_API_KEY not set. Skipping Datadog metrics submission."
110+
)
111+
return
112+
113+
try:
114+
configuration = Configuration()
115+
configuration.api_key["apiKeyAuth"] = dd_api_key
116+
configuration.server_variables["site"] = dd_site
117+
118+
timestamp = int(time.time())
119+
series = []
120+
121+
for item_type, operations in self.stats.items():
122+
for operation, count in operations.items():
123+
metric_name = f"guidebook.sync.{item_type}.{operation}"
124+
series.append(
125+
MetricSeries(
126+
metric=metric_name,
127+
type=MetricIntakeType.COUNT,
128+
points=[
129+
MetricPoint(
130+
timestamp=timestamp,
131+
value=float(count),
132+
)
133+
],
134+
tags=[f"guide:{GUIDE_NAME}"],
135+
)
136+
)
137+
138+
if series:
139+
with ApiClient(configuration) as api_client:
140+
api_instance = MetricsApi(api_client)
141+
body = MetricPayload(
142+
series=series,
143+
)
144+
response = api_instance.submit_metrics(body=body)
145+
logger.info("Successfully sent metrics to Datadog")
146+
logger.debug(f"Datadog response: {response}")
147+
else:
148+
logger.debug("No metrics to send to Datadog")
149+
150+
except Exception as e:
151+
logger.error(f"Failed to send metrics to Datadog: {e}")
152+
153+
54154
class OurJSON:
55155
rooms = set()
56156
tracks = set()
@@ -165,11 +265,21 @@ class GuideBook:
165265

166266
REGIONED_MAP = "Pasadena-Convention-Center-Map-1000-72-fs8"
167267

168-
def __init__(self, logger, update, dryrun, max_deletes, key, x_key=None):
268+
def __init__(
269+
self,
270+
logger,
271+
update,
272+
dryrun,
273+
max_deletes,
274+
key,
275+
stats_tracker,
276+
x_key=None,
277+
):
169278
self.logger = logger
170279
self.update = update
171280
self.dryrun = dryrun
172281
self.max_deletes = max_deletes
282+
self.stats = stats_tracker
173283
self.headers = {"Authorization": "JWT " + key}
174284
self.guide = self.get_guide()
175285
self.tracks = self.get_things("tracks")
@@ -308,6 +418,8 @@ def add_track(self, track, update, tid):
308418
"color": self.COLOR_MAP[track].upper(),
309419
}
310420
self.tracks[track] = self.add_thing("tracks", track, data, update, tid)
421+
operation = "updated" if update else "added"
422+
self.stats.increment("tracks", operation)
311423

312424
def setup_tracks(self, tracks):
313425
"""
@@ -344,6 +456,8 @@ def add_room(self, room, update, rid):
344456
"location_type": 2, # not google maps
345457
}
346458
self.rooms[room] = self.add_thing("rooms", room, data, update, rid)
459+
operation = "updated" if update else "added"
460+
self.stats.increment("rooms", operation)
347461

348462
def setup_rooms(self, rooms):
349463
"""
@@ -382,6 +496,8 @@ def add_x_map_region(self, map_region, update, rid, location_id):
382496
"relative_height": map_region["h"],
383497
}
384498
self.add_thing("x-map-regions", name, data, update, rid)
499+
operation = "updated" if update else "added"
500+
self.stats.increment("map_regions", operation)
385501

386502
def get_x_map_region_for_room(self, location_id):
387503
return next(
@@ -545,6 +661,8 @@ def add_session(self, session, original_session=None):
545661
s = self.add_thing("sessions", name, data, update, sid)
546662
self.sessions_by_nid[session["nid"]] = s
547663
self.sessions_by_name[name] = s
664+
operation = "updated" if update else "added"
665+
self.stats.increment("sessions", operation)
548666

549667
def normalize_html(self, html):
550668
"""
@@ -706,6 +824,7 @@ def delete_session(self, session):
706824
self.logger.error("Failed to delete")
707825
self.logger.error("RESPONSE: %s" % response.json())
708826
sys.exit(1)
827+
self.stats.increment("sessions", "deleted")
709828

710829
def delete_sessions(self):
711830
self.logger.warning("Deleting all sessions")
@@ -735,6 +854,7 @@ def delete_track(self, track):
735854
self.logger.error("Failed to delete")
736855
self.logger.error("RESPONSE: %s" % response.json())
737856
sys.exit(1)
857+
self.stats.increment("tracks", "deleted")
738858

739859
def delete_tracks(self):
740860
self.logger.warning("Deleting all tracks")
@@ -762,6 +882,7 @@ def delete_room(self, room):
762882
self.logger.error("Failed to delete")
763883
self.logger.error("RESPONSE: %s" % response.json())
764884
sys.exit(1)
885+
self.stats.increment("rooms", "deleted")
765886

766887
def delete_rooms(self):
767888
self.logger.warning("Deleting all rooms")
@@ -889,6 +1010,7 @@ def main(debug, update, delete_all, feed, dryrun, max_deletes):
8891010
logger.addHandler(ch)
8901011

8911012
key, x_key = get_tokens(logger)
1013+
stats_tracker = StatsTracker()
8921014

8931015
if delete_all:
8941016
print("WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") # noqa: E999
@@ -898,7 +1020,9 @@ def main(debug, update, delete_all, feed, dryrun, max_deletes):
8981020
else:
8991021
ourdata = OurJSON(feed, logger)
9001022

901-
ourguide = GuideBook(logger, update, dryrun, max_deletes, key, x_key=x_key)
1023+
ourguide = GuideBook(
1024+
logger, update, dryrun, max_deletes, key, stats_tracker, x_key=x_key
1025+
)
9021026
if delete_all:
9031027
ourguide.delete_all()
9041028
else:
@@ -912,6 +1036,10 @@ def main(debug, update, delete_all, feed, dryrun, max_deletes):
9121036
# unclear exactly when this is needed.
9131037
ourguide.publish_updates()
9141038

1039+
# Log and send statistics
1040+
stats_tracker.log_stats(logger)
1041+
stats_tracker.send_to_datadog(logger, dryrun=dryrun)
1042+
9151043

9161044
if __name__ == "__main__":
9171045
main()

0 commit comments

Comments
 (0)