

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# 부록 B — 카이 제곱 계산의 예
<a name="appendix-b-example-chi-squared-calculation"></a>

 다음은 오류 지표를 수집하고 데이터에 대해 카이 제곱 검정을 수행하는 예제입니다. 코드는 프로덕션 준비나 필요한 오류 처리를 수행하지는 않지만 로직 작동 방식에 대한 개념 증명을 제공합니다. 필요에 맞게 이 예제를 업데이트해야 합니다.

 먼저 Amazon EventBridge의 예약된 이벤트에 의해 1분마다 Lambda 함수를 간접적으로 호출합니다. 이벤트 콘텐츠는 다음 데이터로 구성됩니다.

```
{ 
  "timestamp": "2023-03-15T15:26:37.527Z", 
  "namespace": "multi-az/frontend", 
  "metricName": "5xx", 
  "dimensions": [ 
    { "Name": "Region", "Value": "us-east-1" }, 
    { "Name": "Controller", "Value": "Home" }, 
    { "Name": "Action", "Value": "Index" } 
  ], 
  "period": 60, 
  "stat": "Sum", 
  "unit": "Count", 
  "chiSquareMetricName": "multi-az/chi-squared", 
  "azs": [ "use1-az2", "use1-az4", "use1-az6" ] 
}
```

 이 데이터는 적절한 CloudWatch 지표(예: 네임스페이스, 지표 이름, 차원)를 검색하는 데 필요한 공통 데이터를 지정한 다음 각 가용 영역에 대한 카이 제곱 결과를 게시하는 데 사용됩니다. Lambda 함수의 코드는 Python 3.9를 사용하여 다음과 같이 표시됩니다. 상위 수준에서 이전 1분간 지정된 CloudWatch 측정치를 수집하고, 해당 데이터에 대해 카이 제곱 테스트를 실행한 다음, 지정된 각 가용 영역에 대한 테스트 결과에 대한 CloudWatch 측정치를 게시합니다.

```
import os
import boto3
import datetime
import copy
import json
from datetime import timedelta
from scipy.stats import chisquare
from aws_embedded_metrics import metric_scope

cw_client = boto3.client("cloudwatch", os.environ.get("AWS_REGION", "us-east-1"))

@metric_scope
def handler(event, context, metrics):
    metrics.set_property("Event", json.loads(json.dumps(event, default = str)))
    time = datetime.datetime.strptime(event["timestamp"], "%Y-%m-%dT%H:%M:%S.%fZ")
    
    # Round down to the previous minute
    end: datetime = roundTime(time)

    # Subtract a minute for the start
    start: datetime = end - timedelta(minutes = 1)

    # Get all the metrics that match the query
    results = get_all_metrics(event, start, end, metrics)
    metrics.set_property("MetricCounts", results)

    # Calculate the chi squared result
    chi_sq_result = chisquare(list(results.values()))
    expected = sum(list(results.values())) / len(results.values())
    metrics.set_property("ChiSquaredResult", chi_sq_result)

    # Put the chi square metrics into CloudWatch
    put_all_metrics(event, results, chi_sq_result[1], expected, start, metrics)

def get_all_metrics(detail: dict, start: datetime, end: datetime, metrics):
    """
    Gets all of the error metrics for each AZ specified
    """
    metric_query = {
        "MetricDataQueries": [
        ],
        "StartTime": start,
        "EndTime": end
    }

    for az in detail["azs"]:

        dim = copy.deepcopy(detail["dimensions"])
        dim.append({"Name": "AZ-ID", "Value": az})

        query = {
            "Id": az.replace("-", "_"),
            "MetricStat": {
                "Metric": {
                    "Namespace": detail["namespace"],
                    "MetricName": detail["metricName"],
                    "Dimensions": dim
                },
                "Period": int(detail["period"]),
                "Stat": detail["stat"],
                "Unit": detail["unit"]
            },
            "Label": az,
            "ReturnData": True
        }

        metric_query["MetricDataQueries"].append(query)

    metrics.set_property("GetMetricRequest", json.loads(json.dumps(metric_query, default=str)))
    next_token: str = None
    results = {}

    while True:
        if next_token is not None:
            metric_query["NextToken"] = next_token

        data = cw_client.get_metric_data(**metric_query)

        if next_token is not None:
            metrics.set_property("GetMetricResult::" + next_token, json.loads(json.dumps(data, default = str)))
        else:
            metrics.set_property("GetMetricResult", json.loads(json.dumps(data, default = str)))

        for item in data["MetricDataResults"]:
            key = item["Id"].replace("_", "-")
            if key not in results:
              results[key] = 0

            results[key] += sum(item["Values"])

        if "NextToken" in data:
            next_token = data["NextToken"]

        if next_token is None:
            break

    return results

def put_all_metrics(detail: dict, results: dict, chi_sq_value: float, expected: float, timestamp: datetime, metrics):  
    """
    Adds the chi squared metric for all AZs to CloudWatch
    """
    farthest_from_expected = None
    if len(results) > 0:
        keys = list(results.keys())
        farthest_from_expected = keys[0]

        for key in keys:
            if abs(results[key] - expected) > abs(results[farthest_from_expected] - expected):
               farthest_from_expected = key

    metric_query = {
        "Namespace": detail["namespace"],
        "MetricData": []
    }

    for az in detail["azs"]:
        dim = copy.deepcopy(detail["dimensions"])
        dim.append({"Name": "AZ-ID", "Value": az})

        query = {
            "MetricName": detail["chiSquareMetricName"],
            "Dimensions": dim,
            "Timestamp": timestamp,
        }

        if chi_sq_value <= 0.05 and az == farthest_from_expected:
            query["Value"] = 1
        else:
            query["Value"] = 0

        metric_query["MetricData"].append(query)

    metrics.set_property("PutMetricRequest", json.loads(json.dumps(metric_query, default = str)))

    cw_client.put_metric_data(**metric_query)

def roundTime(dt=None, roundTo=60):
   """Round a datetime object to any time lapse in seconds
   dt : datetime.datetime object, default now.
   roundTo : Closest number of seconds to round to, default 1 minute.
   """
   if dt == None : dt = datetime.datetime.now()
   seconds = (dt.replace(tzinfo=None) - dt.min).seconds
   rounding = (seconds+roundTo/2) // roundTo * roundTo
   return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond)
```

 그런 다음 AZ별로 경보를 생성할 수 있습니다. 다음 예제는 최대값이 1인 연속 1분 데이터 요소 3개에 대한 `use1-az2` 신호와 경보를 보여줍니다 (1은 카이 제곱 검정을 통해 오류율의 통계적으로 유의한 편차가 확인될 때 게시되는 지표).

```
{
    "Type": "AWS::CloudWatch::Alarm",
    "Properties": {
        "AlarmName": "use1-az2-chi-squared",
        "ActionsEnabled": true,
        "OKActions": [],
        "AlarmActions": [],
        "InsufficientDataActions": [],
        "MetricName": "multi-az/chi-squared",
        "Namespace": "multi-az/frontend",
        "Statistic": "Maximum",
        "Dimensions": [
            {
                "Name": "AZ-ID",
                "Value": "use1-az2"
            },
            {
                "Name": "Action",
                "Value": "Index"
            },
            {
                "Name": "Region",
                "Value": "us-east-1"
            },
            {
                "Name": "Controller",
                "Value": "Home"
            }
        ],
        "Period": 60,
        "EvaluationPeriods": 3,
        "DatapointsToAlarm": 3,
        "Threshold": 1,
        "ComparisonOperator": "GreaterThanOrEqualToThreshold",
        "TreatMissingData": "missing"
    }
}
```

 m-of-n 경보를 만들고 이 두 경보를 복합 경보와 결합할 수도 있습니다. 또한 각 가용 영역에 있는 컨트롤러/액션 조합 또는 마이크로서비스에 대해 동일한 경보를 생성해야 합니다. 마지막으로, [이상치 감지를 사용한 장애 감지](failure-detection-using-outlier-detection.md)에서와 같이 각 컨트롤러/작업 조합에 대한 가용 영역별 경보에 카이 제곱 복합 경보를 추가할 수 있습니다.