Skip to content
This repository has been archived by the owner on Sep 12, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1146 from chaos-genius/develop
Browse files Browse the repository at this point in the history
release: v0.11.0
  • Loading branch information
manassolanki authored Oct 4, 2022
2 parents acff55f + 6fb0481 commit d6b829f
Show file tree
Hide file tree
Showing 30 changed files with 4,590 additions and 3,474 deletions.
1 change: 1 addition & 0 deletions .github/workflows/greetings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/first-interaction@v1
continue-on-error: true
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Hello there, thanks for opening your first issue. We welcome you to the community!'
Expand Down
58 changes: 27 additions & 31 deletions chaos_genius/alerts/anomaly_alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
from chaos_genius.alerts.slack import anomaly_alert_slack
from chaos_genius.alerts.utils import (
AlertException,
change_message_from_percent,
find_percentage_change,
human_readable,
send_email_using_template,
Expand Down Expand Up @@ -79,6 +78,8 @@ class AnomalyPointOriginal(BaseModel):

# y-value of point
y: float
# model prediction (expected value)
yhat: Optional[float]
# lower bound of expected value
yhat_lower: float
# upper bound of expected value
Expand All @@ -102,8 +103,8 @@ class AnomalyPointOriginal(BaseModel):
data_datetime: datetime.datetime

@property
def expected_value(self) -> str:
"""Expected values represented in a string."""
def expected_range(self) -> str:
"""Expected values range represented in a string."""
return f"{self.yhat_lower} to {self.yhat_upper}"

@property
Expand Down Expand Up @@ -202,10 +203,6 @@ class AnomalyPoint(AnomalyPointOriginal):
severity: int
# previous data point (y-value)
previous_value: Optional[float]
# percentage change from previous point
percent_change: Union[StrictFloat, StrictInt, str]
# human readable message describing the percent_change
change_message: str

relevant_subdims_: Optional[List]

Expand All @@ -232,17 +229,13 @@ def _from_original_single(
relevant_subdims: Optional[List["AnomalyPoint"]],
) -> "AnomalyPoint":
y = round(point.y, 2)
yhat = round(point.yhat, 2) if point.yhat is not None else None
yhat_lower = round(point.yhat_lower, 2)
yhat_upper = round(point.yhat_upper, 2)
severity = round(point.severity)

series_type = point.series_type

percent_change = find_percentage_change(
point.y, previous_anomaly_point.y if previous_anomaly_point else None
)
change_message = change_message_from_percent(percent_change)

previous_value = (
round(previous_anomaly_point.y, 2)
if previous_anomaly_point is not None
Expand All @@ -251,6 +244,7 @@ def _from_original_single(

return AnomalyPoint(
y=y,
yhat=yhat,
yhat_lower=yhat_lower,
yhat_upper=yhat_upper,
severity=severity,
Expand All @@ -260,8 +254,6 @@ def _from_original_single(
created_at=point.created_at,
data_datetime=point.data_datetime,
previous_value=previous_value,
percent_change=percent_change,
change_message=change_message,
relevant_subdims_=relevant_subdims,
)

Expand Down Expand Up @@ -375,7 +367,8 @@ class AnomalyPointFormatted(AnomalyPoint):
alert_channel_conf: Any

formatted_date: str
formatted_change_percent: str
percent_change: Union[StrictFloat, StrictInt, str]
percent_change_formatted: str

is_hourly: bool

Expand All @@ -395,24 +388,25 @@ def _from_point_single(
dt_format = ALERT_READABLE_DATE_FORMAT
formatted_date = point.data_datetime.strftime(dt_format)

formatted_change_percent = point.percent_change
if isinstance(point.percent_change, (int, float)):
percent_change = find_percentage_change(point.y, point.yhat)
percent_change_formatted = percent_change
if isinstance(percent_change, (int, float)):
# TODO: decide on this and simplify
change_percent = (
f"{point.percent_change:.0f}"
if abs(point.percent_change) < 10
else f"{point.percent_change:.0f}"
f"{percent_change:.0f}"
if abs(percent_change) < 10
else f"{percent_change:.0f}"
)
if point.percent_change > 0:
formatted_change_percent = f"{change_percent}%"
if percent_change > 0:
percent_change_formatted = f"{change_percent}%"
else:
formatted_change_percent = f"{change_percent[1:]}%"
percent_change_formatted = f"{change_percent[1:]}%"
if (
isinstance(point.percent_change, str)
and point.percent_change.endswith("inf")
and point.previous_value is not None
isinstance(percent_change, str)
and percent_change.endswith("inf")
and point.yhat is not None
):
formatted_change_percent = f"{point.percent_change}%"
percent_change_formatted = f"{percent_change}%"

is_hourly = time_series_frequency is not None and time_series_frequency == "H"

Expand Down Expand Up @@ -442,7 +436,8 @@ def _from_point_single(
alert_channel=alert_channel,
alert_channel_conf=alert_channel_conf,
formatted_date=formatted_date,
formatted_change_percent=str(formatted_change_percent),
percent_change=percent_change,
percent_change_formatted=str(percent_change_formatted),
is_hourly=is_hourly,
)

Expand Down Expand Up @@ -1120,8 +1115,8 @@ def make_anomaly_data_csv(
anomaly_df.sort_values(by="severity", inplace=True, ascending=False)

# this is a property that is calculated, so it needs to be assigned separately
anomaly_df["expected_value"] = [
point.expected_value for point in all_anomaly_points
anomaly_df["expected_range"] = [
point.expected_range for point in all_anomaly_points
]
# this is a property that is calculated, so it needs to be assigned separately
anomaly_df["series_type"] = [point.series_type_name for point in all_anomaly_points]
Expand Down Expand Up @@ -1161,6 +1156,7 @@ def _format_anomaly_point_for_template(

def top_anomalies(points: Iterable[TAnomalyPointOrig], n=10) -> List[TAnomalyPointOrig]:
"""Returns top n anomalies according to severity."""
# TODO: how to incorporate impact here?
return heapq.nlargest(n, points, key=lambda point: point.severity)


Expand All @@ -1175,6 +1171,6 @@ def iterate_over_all_points(
if point.is_overall or (point.is_subdim and include_subdims):
yield point

if point.is_overall and point.relevant_subdims is not None:
if point.is_overall and point.relevant_subdims is not None and include_subdims:
for subdim_point in point.relevant_subdims:
yield subdim_point
12 changes: 6 additions & 6 deletions chaos_genius/alerts/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
ANOMALY_TABLE_COLUMN_NAMES_MAPPER = {
"series_type": "Dimension",
"data_datetime": "Time of Occurrence",
"y": "Value",
"y": "Actual Value",
"yhat": "Expected Value",
"severity": "Severity Score",
"change_message": "Change",
"expected_value": "Expected Value",
"expected_range": "Expected Range",
"previous_value": "Previous Value",
}
ANOMALY_REPORT_COLUMN_NAMES_MAPPER = dict(
Expand All @@ -36,11 +36,11 @@
ANOMALY_TABLE_COLUMN_NAMES_ORDERED = [
"series_type",
"data_datetime",
"y",
"previous_value",
"change_message",
"y",
"yhat",
"severity",
"expected_value",
"expected_range",
]
ANOMALY_REPORT_COLUMN_NAMES_ORDERED = ["kpi_name"] + ANOMALY_TABLE_COLUMN_NAMES_ORDERED

Expand Down
181 changes: 27 additions & 154 deletions chaos_genius/alerts/email_templates/common.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,169 +54,42 @@
{% macro anomaly_point_formatting(point, kpi_link_prefix=none) -%}
{% set include_kpi_name = kpi_link_prefix is not none %}

{% if point.previous_value is none or point.y == point.previous_value -%}
<strong style="color: orange;">
∿ Anomalous behavior
</strong>
{% if point.percent_change is string %}
<strong style="color: orange;">∿ Anomaly detected</strong>
{% elif point.percent_change >= 0 %}
<strong style="color: rgb(6, 182, 212);">↑ {{ point.percent_change_formatted }} higher </strong> <span style="{{ non_important_text }}">than expected</span>
{% else %}
<strong style="color: orange;">↓ {{ point.percent_change_formatted }} lower </strong> <span style="{{ non_important_text }}">than expected</span>
{% endif %}

{% if include_kpi_name -%}
<span style="{{ non_important_text }}">
<span style="{{ non_important_text }}"> - </span>

in
{% if include_kpi_name -%}
{{ kpi_name_link(kpi_link_prefix, point) }}
{% endif %}

</span>
{% if point.is_subdim %}
{{ subdim_name_link(point) }}
{% endif %}

{{ kpi_name_link(kpi_link_prefix, point) }}
<span style="{{ non_important_text }}">
changed to
</span>

{% if point.is_subdim %}
{{ subdim_name_link(point) }}
{% endif %}
{% else -%}
<span style="{{ non_important_text }}">
<strong style="{{ important_text }}">{{point.y_readable}}</strong><!--
detected

</span>

{% if point.is_subdim -%}
<span style="{{ non_important_text }}">

in

</span>

{{ subdim_name_link(point) }}
{%- endif %}
{%- endif %}

-

{% if point.previous_value is none -%}
<span style="{{ non_important_text }}">

changed to

</span>
<strong style="{{ important_text }}">{{point.y_readable}}</strong>

{% if point.is_hourly -%}
<span style="{{ non_important_text }}">
at
</span>
<span style="{{ important_text }}">{{ point.anomaly_time_only }}</span>
{%- endif %}
{% else -%}
{% if point.is_hourly %}
<span style="{{ non_important_text }}">
with constant value
</span>

<strong style="{{ important_text }}">{{point.y_readable}}</strong>

<span style="{{ non_important_text }}">
from
</span>
<span style="{{ important_text }}">{{ point.previous_point_time_only }}</span>

<span style="{{ non_important_text }}">
to
</span>
<span style="{{ important_text }}">{{ point.anomaly_time_only }}</span>
{% else %}
<span style="{{ non_important_text }}">
with same value
</span>

<strong style="{{ important_text }}">{{point.y_readable}}</strong>

<span style="{{ non_important_text }}">
as previous day
</span>
{% endif %}
{%- endif %}

{% else -%}
{% if point.y > point.previous_value %}
<strong style="color: rgb(6, 182, 212);">
↑ {{ point.formatted_change_percent }}
Spike
</strong>
{% elif point.y < point.previous_value %}
<strong style="color: orange;">
↓ {{ point.formatted_change_percent }}
Drop
</strong>
{% endif %}

{% if include_kpi_name %}
<span style="{{ non_important_text }}">

in

</span>

{{ kpi_name_link(kpi_link_prefix, point) }}

{% if point.is_subdim %}
{{ subdim_name_link(point) }}
{% endif %}
{% else %}
<span style="{{ non_important_text }}">

detected

</span>

{% if point.is_subdim %}
<span style="{{ non_important_text }}">

in

</span>

{{ subdim_name_link(point) }}
{% endif %}
{% endif %}

<span style="{{ non_important_text }}">

-

changed

</span>

<span style="{{ non_important_text }}">
to
-->{% if point.is_hourly -%}<!--
--><span style="{{ non_important_text }}">
at
</span>
<span style="{{ important_text }}">{{ point.anomaly_time_only }}</span><!--
-->{%- endif %}<!--
<strong style="{{ important_text }}">{{point.y_readable}}</strong>

{% if point.previous_value_readable is not none -%}

<span style="{{ non_important_text }}">
from
</span>
<span style="{{ important_text }}">{{point.previous_value_readable}}</span>

{%- endif %}

<!--(<span style="{{ important_text }}">{{ point.formatted_change_percent }}</span>)--><!--
-->{% if point.is_hourly -%}
<span style="{{ non_important_text }}">
from
</span>
<span style="{{ important_text }}">{{ point.previous_point_time_only }}</span>

<span style="{{ non_important_text }}">
to
</span>
<span style="{{ important_text }}">{{ point.anomaly_time_only }}</span>
{%- endif %}
{%- endif %}<!--
--><span style="{{ non_important_text }}"><!--
-->. (Expected range: {{ point.yhat_lower_readable }} - {{ point.yhat_upper_readable }}).
</span>

-->{% if point.relevant_subdims -%}
{% if point.relevant_subdims -%}
<ul>
<li>
<span style="{{ non_important_text }}">Reasons for change:</span>
Expand Down
Loading

0 comments on commit d6b829f

Please sign in to comment.