Skip to content

Commit

Permalink
Add telemetry (initialised when intake.cat.access_nri entrypoint acce…
Browse files Browse the repository at this point in the history
…ssed)
  • Loading branch information
charles-turner-1 committed Dec 17, 2024
1 parent d74943d commit 1b6f54a
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 0 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies = [
"jsonschema",
"pooch",
"xarray",
"httpx>=0.28"
]
dynamic = ["version"]

Expand Down
21 changes: 21 additions & 0 deletions src/access_nri_intake/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,34 @@

import intake
import intake.catalog
from IPython import get_ipython
from IPython.core.magic import register_line_magic

from access_nri_intake.utils import get_catalog_fp

from .telemetry import capture_datastore_searches

CATALOG_NAME_FORMAT = (
r"^v(?P<yr>2[0-9]{3})\-(?P<mon>1[0-2]|0[1-9])\-(?P<day>0[1-9]|[1-2][0-9]|3[0-1])$"
)


def load_ipython_extension(ipython):
@register_line_magic("capture_func_calls")
def capture_func_calls(info):
"""
Returns the function calls from the code in the cell
"""
ipython.events.register("pre_run_cell", capture_datastore_searches)
print("Function calls will be captured for the next cell execution")


# Register the extension
ip = get_ipython()
if ip:
load_ipython_extension(ip)
ip.run_line_magic("capture_func_calls", "")

try:
data = intake.open_catalog(get_catalog_fp()).access_nri
except FileNotFoundError:
Expand Down
82 changes: 82 additions & 0 deletions src/access_nri_intake/data/telemetry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import ast
import asyncio
import warnings

import httpx
from IPython import get_ipython

TELEMETRY_SERVER_URL = "https://intake-telemetry-bb870061f91a.herokuapp.com"


def send_api_request(function_name, kwargs):
telemetry_data = {
"name": f"CT_testing_{function_name}_ipy_extensions",
"search": kwargs,
}

endpoint = f"{TELEMETRY_SERVER_URL}/telemetry/update"

async def send_telemetry(data):
headers = {"Content-Type": "application/json"}
async with httpx.AsyncClient() as client:
try:
response = await client.post(endpoint, json=data, headers=headers)
response.raise_for_status()

print(f"Telemetry data sent: {response.json()}")
except httpx.RequestError as e:
warnings.warn(
f"Request failed: {e}", category=RuntimeWarning, stacklevel=2
)

# Schedule the telemetry data to be sent in the background
asyncio.create_task(send_telemetry(telemetry_data))
return None


def capture_datastore_searches(info):
"""
Use the AST module to parse the code that we are executing & send an API call
if we
"""
code = info.raw_cell

# Remove lines that contain IPython magic commands
code = "\n".join(
line for line in code.splitlines() if not line.strip().startswith("%")
)

tree = ast.parse(code)
user_namespace = get_ipython().user_ns

for node in ast.walk(tree):
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
func_name = node.func.id
elif isinstance(node.func, ast.Attribute):
# Check if the attribute is a method call on a class instance or a module function
if isinstance(node.func.value, ast.Name):
instance_name = node.func.value.id
method_name = node.func.attr
try:
# Evaluate the instance to get its class name
instance = eval(instance_name, globals(), user_namespace)
class_name = instance.__class__.__name__
func_name = f"{class_name}.{method_name}"
except Exception as e:
print(f"Error evaluating instance: {e}")
continue

if func_name in [
"esm_datastore.search",
]:
# args = [ast.dump(arg) for arg in node.args]
kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in node.keywords}
send_api_request(
func_name,
# args,
kwargs,
)
else:
print(f"Function call: {func_name}; not tracking")

0 comments on commit 1b6f54a

Please sign in to comment.