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(alerts): Add support for prediction to NRQL alert conditions #1271

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
33 changes: 27 additions & 6 deletions pkg/alerts/nrql_conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,19 @@ var (
}
)

type NrqlConditionThresholdPrediction struct {
PredictBy int `json:"predictBy,omitempty"`
PreferPredictionViolation bool `json:"preferPredictionViolation"`
}

// NrqlConditionTerm represents the a single term of a New Relic alert condition.
type NrqlConditionTerm struct {
Operator AlertsNRQLConditionTermsOperator `json:"operator,omitempty"`
Priority NrqlConditionPriority `json:"priority,omitempty"`
Threshold *float64 `json:"threshold"`
ThresholdDuration int `json:"thresholdDuration,omitempty"`
ThresholdOccurrences ThresholdOccurrence `json:"thresholdOccurrences,omitempty"`
Operator AlertsNRQLConditionTermsOperator `json:"operator,omitempty"`
Priority NrqlConditionPriority `json:"priority,omitempty"`
Threshold *float64 `json:"threshold"`
ThresholdDuration int `json:"thresholdDuration,omitempty"`
ThresholdOccurrences ThresholdOccurrence `json:"thresholdOccurrences,omitempty"`
Prediction *NrqlConditionThresholdPrediction `json:"prediction,omitempty"`
}

// NrqlConditionQuery represents the NRQL query object returned in a NerdGraph response object.
Expand Down Expand Up @@ -741,7 +747,7 @@ const (
closeViolationsOnExpiration
expirationDuration
openViolationOnExpiration
ignoreOnExpectedTermination
ignoreOnExpectedTermination
}
signal {
aggregationWindow
Expand All @@ -762,6 +768,17 @@ const (
}
`

graphqlFragmentNrqlStaticConditionFields = `
... on AlertsNrqlStaticCondition {
terms {
prediction {
predictBy
preferPredictionViolation
}
}
}
`

searchNrqlConditionsQuery = `
query($accountId: Int!, $searchCriteria: AlertsNrqlConditionsSearchCriteriaInput, $cursor: String) {
actor {
Expand All @@ -773,6 +790,7 @@ const (
nrqlConditions {` +
graphqlNrqlConditionStructFields +
graphqlFragmentNrqlBaselineConditionFields +
graphqlFragmentNrqlStaticConditionFields +
`} } } } } }`

getNrqlConditionQuery = `
Expand All @@ -783,6 +801,7 @@ const (
nrqlCondition(id: $id) {` +
graphqlNrqlConditionStructFields +
graphqlFragmentNrqlBaselineConditionFields +
graphqlFragmentNrqlStaticConditionFields +
`} } } } }`

// Baseline
Expand All @@ -806,12 +825,14 @@ const (
mutation($accountId: Int!, $policyId: ID!, $condition: AlertsNrqlConditionStaticInput!) {
alertsNrqlConditionStaticCreate(accountId: $accountId, policyId: $policyId, condition: $condition) {` +
graphqlNrqlConditionStructFields +
graphqlFragmentNrqlStaticConditionFields +
` } }`

// Static
updateNrqlConditionStaticMutation = `
mutation($accountId: Int!, $id: ID!, $condition: AlertsNrqlConditionUpdateStaticInput!) {
alertsNrqlConditionStaticUpdate(accountId: $accountId, id: $id, condition: $condition) {` +
graphqlNrqlConditionStructFields +
graphqlFragmentNrqlStaticConditionFields +
` } }`
)
79 changes: 79 additions & 0 deletions pkg/alerts/nrql_conditions_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var (
nrqlConditionBaseSlideBy = 30 // needed for setting pointer
nrqlConditionEvaluationDelay = 60 // needed for setting pointer
nrqlConditionTitleTemplate = "Title {{ createdAt }}" // needed for setting pointer
nrqlConditionPredictBy = 7200 // needed for setting pointer
nrqlConditionCreateBase = NrqlConditionCreateBase{
Description: "test description",
Enabled: true,
Expand Down Expand Up @@ -896,3 +897,81 @@ func TestIntegrationNrqlConditions_IgnoreOnExpectedTermination(t *testing.T) {
}
}()
}

func TestIntegrationNrqlConditions_Prediction(t *testing.T) {
t.Parallel()

testAccountID, err := mock.GetTestAccountID()
if err != nil {
t.Skipf("%s", err)
}

var nrqlConditionCreateWithPrediction = NrqlConditionCreateBase{
Enabled: true,
Name: fmt.Sprintf("test-nrql-condition-%s", testNrqlConditionRandomString),
Nrql: NrqlConditionCreateQuery{
Query: "SELECT rate(sum(apm.service.cpu.usertime.utilization), 1 second) * 100 as cpuUsage FROM Metric WHERE appName like 'Dummy App'",
DataAccountId: &testAccountID,
},
Terms: []NrqlConditionTerm{
{
Threshold: &nrqlConditionBaseThreshold,
ThresholdOccurrences: ThresholdOccurrences.AtLeastOnce,
ThresholdDuration: 600,
Operator: AlertsNRQLConditionTermsOperatorTypes.ABOVE,
Priority: NrqlConditionPriorities.Critical,
Prediction: &NrqlConditionThresholdPrediction{
PredictBy: nrqlConditionPredictBy,
},
},
},
ViolationTimeLimitSeconds: 3600,
Signal: &AlertsNrqlConditionCreateSignal{
AggregationWindow: &nrqlConditionBaseAggWindow,
FillOption: &AlertsFillOptionTypes.STATIC,
FillValue: &nrqlConditionBaseSignalFillValue,
EvaluationDelay: &nrqlConditionEvaluationDelay,
AggregationMethod: &nrqlConditionBaseAggMethod,
AggregationDelay: &nrqlConditionBaseAggDelay,
},
}

var (
randStr = mock.RandSeq(5)
createPredictionInput = NrqlConditionCreateInput{
NrqlConditionCreateBase: nrqlConditionCreateWithPrediction,
}
)

// Setup
client := newIntegrationTestClient(t)
testPolicy := AlertsPolicyInput{
IncidentPreference: AlertsIncidentPreferenceTypes.PER_POLICY,
Name: fmt.Sprintf("test-alert-policy-%s", randStr),
}
policy, err := client.CreatePolicyMutation(testAccountID, testPolicy)
require.NoError(t, err)

// Test: Create (static condition with forecast field)
createdStaticWithPrediction, err := client.CreateNrqlConditionStaticMutation(testAccountID, policy.ID, createPredictionInput)
require.NoError(t, err)
require.NotNil(t, createdStaticWithPrediction)
require.NotNil(t, createdStaticWithPrediction.ID)
require.NotNil(t, createdStaticWithPrediction.PolicyID)
require.NotNil(t, createdStaticWithPrediction.Terms[0].Prediction)
require.Equal(t, nrqlConditionPredictBy, createdStaticWithPrediction.Terms[0].Prediction.PredictBy)

// Test: Get (static condition with dataAccountId field)
readResult, err := client.GetNrqlConditionQuery(testAccountID, createdStaticWithPrediction.ID)
require.NoError(t, err)
require.NotNil(t, readResult)
require.Equal(t, nrqlConditionPredictBy, readResult.Terms[0].Prediction.PredictBy)

// Deferred teardown
defer func() {
_, err := client.DeletePolicyMutation(testAccountID, policy.ID)
if err != nil {
t.Logf("error cleaning up alert policy %s (%s): %s", policy.ID, policy.Name, err)
}
}()
}
Loading