Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Report TF custom plan file usage in telemetry #5825

Merged
merged 5 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading