Skip to content

Commit 0db6b2e

Browse files
committed
feat: add script for generating plot of OSBv2 users over time
1 parent 9f74956 commit 0db6b2e

1 file changed

Lines changed: 129 additions & 0 deletions

File tree

scripts/get-user-metrics.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script to get user metrics and plot them. This can only be used by admins,
4+
since it requires access to the complete user list in KeyCloak.
5+
6+
File: get-user-metrics.py
7+
8+
Copyright 2025 Ankur Sinha
9+
Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
10+
"""
11+
12+
import sys
13+
import datetime
14+
import requests
15+
import logging
16+
import matplotlib
17+
import matplotlib.pyplot as plt
18+
from dateutil.relativedelta import relativedelta
19+
20+
logging.basicConfig(level=logging.DEBUG)
21+
logger = logging.getLogger(__name__)
22+
logger.setLevel(logging.DEBUG)
23+
24+
25+
def run(keycloak_user, keycloak_password):
26+
"""Main runner method.
27+
28+
:param keycloak_user: keycloak username
29+
:type keycloak_user: string
30+
:param keycloak_password: keycloak password
31+
:type keycloak_password: string
32+
33+
"""
34+
keycloak_url = "https://accounts.v2.opensourcebrain.org/auth"
35+
keycloak_realm = "osb2"
36+
logger.debug(f"Username: {keycloak_user} Password: {keycloak_password}")
37+
38+
resp = requests.post(
39+
f"{keycloak_url}/realms/master/protocol/openid-connect/token",
40+
data={
41+
"client_id": "admin-cli",
42+
"username": keycloak_user,
43+
"password": keycloak_password,
44+
"grant_type": "password",
45+
},
46+
)
47+
resp.raise_for_status()
48+
data = resp.json()
49+
access_token = data["access_token"]
50+
logger.debug(access_token)
51+
52+
auth_headers = {"Authorization": f"Bearer {access_token}"}
53+
54+
resp = requests.get(
55+
f"{keycloak_url}/admin/realms/{keycloak_realm}/users/count",
56+
headers=auth_headers,
57+
)
58+
59+
resp.raise_for_status()
60+
data = resp.json()
61+
total_users = data
62+
print(f">>> Number of users: {total_users}")
63+
64+
resp = requests.get(
65+
f"{keycloak_url}/admin/realms/{keycloak_realm}/users",
66+
headers=auth_headers,
67+
params={"max": total_users},
68+
)
69+
70+
resp.raise_for_status()
71+
data = resp.json()
72+
get_user_trends(data)
73+
74+
75+
def get_user_trends(data, timewindow=2):
76+
"""Get trends for user registration, and plot figure
77+
78+
:param data: user data from keycloak
79+
:type data: list[dict]
80+
:param timewindow: size of window/bin for plotting, in months
81+
:type timewindow: int
82+
"""
83+
time_now = datetime.datetime.now()
84+
trend_start = datetime.datetime(2021, 1, 1, 0, 0, 0)
85+
86+
# note, milliseconds
87+
creation_time_stamps = sorted([au["createdTimestamp"] / 1000 for au in data])
88+
89+
plot_data_x = []
90+
plot_data_y = []
91+
92+
index = 0
93+
trend_point = trend_start
94+
for ts in creation_time_stamps:
95+
index += 1
96+
if ts > trend_point.timestamp():
97+
plot_data_x.append(trend_point)
98+
plot_data_y.append(index)
99+
trend_point += relativedelta(months=timewindow)
100+
101+
assert index == len(creation_time_stamps)
102+
plot_data_x.append(time_now)
103+
plot_data_y.append(index)
104+
105+
fig, ax = plt.subplots()
106+
plt.title("OSBv2 users over time")
107+
ax.plot(
108+
plot_data_x,
109+
plot_data_y,
110+
)
111+
ax.set_xlabel("Date")
112+
ax.set_ylabel("Number of users")
113+
ax.spines["top"].set_visible(False)
114+
ax.spines["right"].set_visible(False)
115+
ax.xaxis.set(
116+
major_locator=matplotlib.dates.MonthLocator(interval=timewindow),
117+
major_formatter=matplotlib.dates.DateFormatter("%Y-%m"),
118+
)
119+
120+
plt.xticks(rotation=90)
121+
plt.tight_layout()
122+
plt.show()
123+
124+
125+
if __name__ == "__main__":
126+
if len(sys.argv) < 3:
127+
print("Error: two arguments required: username and password")
128+
sys.exit(-1)
129+
run(sys.argv[1], sys.argv[2])

0 commit comments

Comments
 (0)