Skip to content

Commit

Permalink
feat: Report TF custom plan file usage in telemetry (#5825)
Browse files Browse the repository at this point in the history
* feat: Report TF custom plan file usage in telemetry

* Read property from config
  • Loading branch information
mildaniel authored Aug 23, 2023
1 parent a0d3ec5 commit cae580d
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 13 deletions.
22 changes: 22 additions & 0 deletions samcli/commands/_utils/custom_options/hook_name_option.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import logging
import os
from typing import Any, Mapping

import click

Expand All @@ -18,9 +19,12 @@
)
from samcli.lib.hook.exceptions import InvalidHookWrapperException
from samcli.lib.hook.hook_wrapper import IacHookWrapper, get_available_hook_packages_ids
from samcli.lib.telemetry.event import EventName, EventTracker

LOG = logging.getLogger(__name__)

PLAN_FILE_OPTION = "terraform_plan_file"


class HookNameOption(click.Option):
"""
Expand Down Expand Up @@ -66,6 +70,8 @@ def handle_parse_result(self, ctx, opts, args):
# capture exceptions from prepare hook to emit in track_command
c = Context.get_current_context()
c.exception = ex
finally:
record_hook_telemetry(opts, ctx)

return super().handle_parse_result(ctx, opts, args)

Expand Down Expand Up @@ -215,3 +221,19 @@ def _read_parameter_value(param_name, opts, ctx, default_value=None):
Read SAM CLI parameter value either from the parameters list or from the samconfig values
"""
return opts.get(param_name, ctx.default_map.get(param_name, default_value))


def record_hook_telemetry(opts: Mapping[str, Any], ctx: click.Context):
"""
Emit metrics related to hooks based on the options passed into the command
Parameters
----------
opts: Mapping[str, Any]
Mapping between a command line option and its value
ctx: Context
Command context properties
"""
plan_file_param = _read_parameter_value(PLAN_FILE_OPTION, opts, ctx)
if plan_file_param:
EventTracker.track_event(EventName.HOOK_CONFIGURATIONS_USED.value, "TerraformPlanFile")
33 changes: 26 additions & 7 deletions samcli/lib/telemetry/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class EventName(Enum):
SYNC_FLOW_END = "SyncFlowEnd"
BUILD_WORKFLOW_USED = "BuildWorkflowUsed"
CONFIG_FILE_EXTENSION = "SamConfigFileExtension"
HOOK_CONFIGURATIONS_USED = "HookConfigurationsUsed"


class UsedFeature(Enum):
Expand Down Expand Up @@ -61,6 +62,10 @@ class EventType:
]
_WORKFLOWS = [f"{config.language}-{config.dependency_manager}" for config in ALL_CONFIGS]

_HOOK_CONFIGURATIONS = [
"TerraformPlanFile",
]

_event_values = { # Contains allowable values for Events
EventName.USED_FEATURE: [event.value for event in UsedFeature],
EventName.BUILD_FUNCTION_RUNTIME: INIT_RUNTIMES,
Expand All @@ -72,6 +77,7 @@ class EventType:
EventName.SYNC_FLOW_END: _SYNC_FLOWS,
EventName.BUILD_WORKFLOW_USED: _WORKFLOWS,
EventName.CONFIG_FILE_EXTENSION: list(FILE_MANAGER_MAPPER.keys()),
EventName.HOOK_CONFIGURATIONS_USED: _HOOK_CONFIGURATIONS,
}

@staticmethod
Expand Down Expand Up @@ -148,6 +154,7 @@ class EventTracker:
_events: List[Event] = []
_event_lock = threading.Lock()
_session_id: Optional[str] = None
_command_name: Optional[str] = None

MAX_EVENTS: int = 50 # Maximum number of events to store before sending

Expand Down Expand Up @@ -205,8 +212,9 @@ def track_event(
Event(event_name, event_value, thread_id=thread_id, exception_name=exception_name)
)

# Get the session ID (needed for multithreading sending)
EventTracker._set_session_id()
# Get properties from the click context (needed for multithreading sending)
EventTracker._set_context_property("_session_id", "session_id")
EventTracker._set_context_property("_command_name", "command_path")

if len(EventTracker._events) >= EventTracker.MAX_EVENTS:
should_send = True
Expand Down Expand Up @@ -235,17 +243,27 @@ def send_events() -> threading.Thread:
return send_thread

@staticmethod
def _set_session_id() -> None:
def _set_context_property(event_prop: str, context_prop: str) -> None:
"""
Get the session ID from click and save it locally.
Set a click context property in the event so that it is emitted when the metric is sent.
This is required since the event is sent in a separate thread and no longer has access
to the click context that the command was initially called with. As a workaround, we set
the property here first so that it's available when calling the metrics endpoint.
Parameters
----------
event_prop: str
Property name to be stored in the event and consumed when emitting the metric
context_prop: str
Property name for the target property from the context object
"""
if not EventTracker._session_id:
if not getattr(EventTracker, event_prop):
try:
ctx = Context.get_current_context()
if ctx:
EventTracker._session_id = ctx.session_id
setattr(EventTracker, event_prop, getattr(ctx, context_prop))
except RuntimeError:
LOG.debug("EventTracker: Unable to obtain session ID")
LOG.debug("EventTracker: Unable to obtain %s", context_prop)

@staticmethod
def _send_events_in_thread():
Expand All @@ -264,6 +282,7 @@ def _send_events_in_thread():
telemetry = Telemetry()
metric = Metric("events")
metric.add_data("sessionId", EventTracker._session_id)
metric.add_data("commandName", EventTracker._command_name)
metric.add_data("metricSpecificAttributes", msa)
telemetry.emit(metric)

Expand Down
Loading

0 comments on commit cae580d

Please sign in to comment.