Skip to content

Commit

Permalink
Merge d3be627 into c447ce4
Browse files Browse the repository at this point in the history
  • Loading branch information
litleleprikon committed May 27, 2020
2 parents c447ce4 + d3be627 commit 40f7582
Show file tree
Hide file tree
Showing 137 changed files with 3,996 additions and 1,987 deletions.
3 changes: 1 addition & 2 deletions api/controller/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,10 @@ func RemoveContact(database moira.Database, contactID string, userLogin string)

// SendTestContactNotification push test notification to verify the correct contact settings
func SendTestContactNotification(dataBase moira.Database, contactID string) *api.ErrorResponse {
var value float64 = 1
eventData := &moira.NotificationEvent{
ContactID: contactID,
Metric: "Test.metric.value",
Value: &value,
Values: map[string]float64{"t1": 1},
OldState: moira.StateTEST,
State: moira.StateTEST,
Timestamp: date.DateParamToEpoch("now", "", time.Now().Add(-24*time.Hour).Unix(), time.UTC),
Expand Down
3 changes: 1 addition & 2 deletions api/controller/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,10 @@ func RemoveSubscription(database moira.Database, subscriptionID string) *api.Err

// SendTestNotification push test notification to verify the correct notification settings
func SendTestNotification(database moira.Database, subscriptionID string) *api.ErrorResponse {
var value float64 = 1
eventData := &moira.NotificationEvent{
SubscriptionID: &subscriptionID,
Metric: "Test.metric.value",
Value: &value,
Values: map[string]float64{"t1": 1},
OldState: moira.StateTEST,
State: moira.StateTEST,
Timestamp: date.DateParamToEpoch("now", "", time.Now().Add(-24*time.Hour).Unix(), time.UTC),
Expand Down
2 changes: 1 addition & 1 deletion api/controller/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func saveTrigger(dataBase moira.Database, trigger *moira.Trigger, triggerID stri
if err != database.ErrNil {
for metric := range lastCheck.Metrics {
if _, ok := timeSeriesNames[metric]; !ok {
delete(lastCheck.Metrics, metric)
lastCheck.RemoveMetricState(metric)
}
}
} else {
Expand Down
54 changes: 22 additions & 32 deletions api/controller/trigger_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,26 @@ import (

// GetTriggerEvaluationResult evaluates every target in trigger and returns
// result, separated on main and additional targets metrics
func GetTriggerEvaluationResult(dataBase moira.Database, metricSourceProvider *metricSource.SourceProvider, from, to int64, triggerID string, fetchRealtimeData bool) (*metricSource.TriggerMetricsData, *moira.Trigger, error) {
func GetTriggerEvaluationResult(dataBase moira.Database, metricSourceProvider *metricSource.SourceProvider, from, to int64, triggerID string, fetchRealtimeData bool) (map[string][]metricSource.MetricData, *moira.Trigger, error) {
trigger, err := dataBase.GetTrigger(triggerID)
if err != nil {
return nil, nil, err
}
triggerMetrics := metricSource.MakeEmptyTriggerMetricsData()
triggerMetrics := make(map[string][]metricSource.MetricData)
metricsSource, err := metricSourceProvider.GetTriggerMetricSource(&trigger)
if err != nil {
return nil, &trigger, err
}
for i, tar := range trigger.Targets {
fetchResult, err := metricsSource.Fetch(tar, from, to, fetchRealtimeData)
for i, target := range trigger.Targets {
i++ // Increase counter to have trigger names start from t1
fetchResult, err := metricsSource.Fetch(target, from, to, fetchRealtimeData)
if err != nil {
return nil, &trigger, err
}
metricData := fetchResult.GetMetricsData()
if i == 0 {
triggerMetrics.Main = metricData
} else {
triggerMetrics.Additional = append(triggerMetrics.Additional, metricData...)
}

targetName := fmt.Sprintf("t%d", i)
triggerMetrics[targetName] = metricData
}
return triggerMetrics, &trigger, nil
}
Expand All @@ -57,31 +56,22 @@ func GetTriggerMetrics(dataBase moira.Database, metricSourceProvider *metricSour
}
return nil, api.ErrorInternalServer(err)
}
triggerMetrics := dto.TriggerMetrics{
Main: make(map[string][]*moira.MetricValue),
Additional: make(map[string][]*moira.MetricValue),
}
for _, timeSeries := range tts.Main {
values := make([]*moira.MetricValue, 0)
for i := 0; i < len(timeSeries.Values); i++ {
timestamp := timeSeries.StartTime + int64(i)*timeSeries.StepTime
value := timeSeries.GetTimestampValue(timestamp)
if moira.IsValidFloat64(value) {
values = append(values, &moira.MetricValue{Value: value, Timestamp: timestamp})
}
}
triggerMetrics.Main[timeSeries.Name] = values
}
for _, timeSeries := range tts.Additional {
values := make([]*moira.MetricValue, 0)
for i := 0; i < len(timeSeries.Values); i++ {
timestamp := timeSeries.StartTime + int64(i)*timeSeries.StepTime
value := timeSeries.GetTimestampValue(timestamp)
if moira.IsValidFloat64(value) {
values = append(values, &moira.MetricValue{Value: value, Timestamp: timestamp})
triggerMetrics := make(dto.TriggerMetrics)

for targetName, target := range tts {
targetMetrics := make(map[string][]moira.MetricValue)
for _, timeSeries := range target {
values := make([]moira.MetricValue, 0)
for i, l := 0, len(timeSeries.Values); i < l; i++ {
timestamp := timeSeries.StartTime + int64(i)*timeSeries.StepTime
value := timeSeries.GetTimestampValue(timestamp)
if moira.IsValidFloat64(value) {
values = append(values, moira.MetricValue{Value: value, Timestamp: timestamp})
}
}
targetMetrics[timeSeries.Name] = values
}
triggerMetrics.Additional[timeSeries.Name] = values
triggerMetrics[targetName] = targetMetrics
}
return &triggerMetrics, nil
}
Expand Down
4 changes: 2 additions & 2 deletions api/controller/trigger_metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,10 @@ func TestGetTriggerMetrics(t *testing.T) {
dataBase.EXPECT().GetTrigger(triggerID).Return(moira.Trigger{ID: triggerID, Targets: []string{pattern}}, nil)
localSource.EXPECT().IsConfigured().Return(true, nil)
localSource.EXPECT().Fetch(pattern, from, until, false).Return(fetchResult, nil)
fetchResult.EXPECT().GetMetricsData().Return([]*metricSource.MetricData{metricSource.MakeMetricData(metric, []float64{0, 1, 2, 3, 4}, retention, from)})
fetchResult.EXPECT().GetMetricsData().Return([]metricSource.MetricData{*metricSource.MakeMetricData(metric, []float64{0, 1, 2, 3, 4}, retention, from)})
triggerMetrics, err := GetTriggerMetrics(dataBase, sourceProvider, from, until, triggerID)
So(err, ShouldBeNil)
So(*triggerMetrics, ShouldResemble, dto.TriggerMetrics{Main: map[string][]*moira.MetricValue{metric: {{Value: 0, Timestamp: 17}, {Value: 1, Timestamp: 27}, {Value: 2, Timestamp: 37}, {Value: 3, Timestamp: 47}, {Value: 4, Timestamp: 57}}}, Additional: make(map[string][]*moira.MetricValue)})
So(*triggerMetrics, ShouldResemble, dto.TriggerMetrics{"t1": map[string][]moira.MetricValue{metric: {{Value: 0, Timestamp: 17}, {Value: 1, Timestamp: 27}, {Value: 2, Timestamp: 37}, {Value: 3, Timestamp: 47}, {Value: 4, Timestamp: 57}}}})
})

Convey("GetTrigger error", t, func() {
Expand Down
25 changes: 21 additions & 4 deletions api/dto/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package dto
import (
"fmt"
"net/http"
"regexp"
"strconv"
"time"

"github.com/moira-alert/moira"
Expand All @@ -13,6 +15,8 @@ import (
metricSource "github.com/moira-alert/moira/metric_source"
)

var targetNameRegex = regexp.MustCompile("t(\\d+)")

type TriggersList struct {
Page *int64 `json:"page,omitempty"`
Size *int64 `json:"size,omitempty"`
Expand Down Expand Up @@ -62,6 +66,8 @@ type TriggerModel struct {
IsRemote bool `json:"is_remote"`
// If true, first event NODATA → OK will be omitted
MuteNewMetrics bool `json:"mute_new_metrics"`
// A list of targets that have only alone metrics
AloneMetrics map[string]bool `json:"alone_metrics"`
}

// ToMoiraTrigger transforms TriggerModel to moira.Trigger
Expand All @@ -82,6 +88,7 @@ func (model *TriggerModel) ToMoiraTrigger() *moira.Trigger {
Patterns: model.Patterns,
IsRemote: model.IsRemote,
MuteNewMetrics: model.MuteNewMetrics,
AloneMetrics: model.AloneMetrics,
}
}

Expand Down Expand Up @@ -120,6 +127,19 @@ func (trigger *Trigger) Bind(request *http.Request) error {
if err := checkWarnErrorExpression(trigger); err != nil {
return api.ErrInvalidRequestContent{ValidationError: err}
}
for targetName := range trigger.AloneMetrics {
if !targetNameRegex.MatchString(targetName) {
return api.ErrInvalidRequestContent{ValidationError: fmt.Errorf("alone metrics target name should be in pattern: t\\d+")}
}
targetIndexStr := targetNameRegex.FindStringSubmatch(targetName)[0]
targetIndex, err := strconv.Atoi(targetIndexStr)
if err != nil {
return api.ErrInvalidRequestContent{ValidationError: fmt.Errorf("alone metrics target index should be valid number: %w", err)}
}
if targetIndex < 0 || targetIndex > len(trigger.Targets) {
return api.ErrInvalidRequestContent{ValidationError: fmt.Errorf("alone metrics target index should be in range from 1 to length of targets")}
}
}

triggerExpression := expression.TriggerExpression{
AdditionalTargetsValues: make(map[string]float64),
Expand Down Expand Up @@ -323,10 +343,7 @@ func (*SaveTriggerResponse) Render(w http.ResponseWriter, r *http.Request) error
return nil
}

type TriggerMetrics struct {
Main map[string][]*moira.MetricValue `json:"main"`
Additional map[string][]*moira.MetricValue `json:"additional,omitempty"`
}
type TriggerMetrics map[string]map[string][]moira.MetricValue

func (*TriggerMetrics) Render(w http.ResponseWriter, r *http.Request) error {
return nil
Expand Down
2 changes: 1 addition & 1 deletion api/dto/triggers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestExpressionModeMultipleTargetsWarnValue(t *testing.T) {
localSource.EXPECT().GetMetricsTTLSeconds().Return(int64(3600)).AnyTimes()
localSource.EXPECT().Fetch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(fetchResult, nil).AnyTimes()
fetchResult.EXPECT().GetPatterns().Return(make([]string, 0), nil).AnyTimes()
fetchResult.EXPECT().GetMetricsData().Return([]*metricSource.MetricData{metricSource.MakeMetricData("", []float64{}, 0, 0)}).AnyTimes()
fetchResult.EXPECT().GetMetricsData().Return([]metricSource.MetricData{*metricSource.MakeMetricData("", []float64{}, 0, 0)}).AnyTimes()

request, _ := http.NewRequest("PUT", "/api/trigger", nil)
request.Header.Set("Content-Type", "application/json")
Expand Down
2 changes: 1 addition & 1 deletion api/handler/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func trigger(router chi.Router) {
})
router.Route("/metrics", triggerMetrics)
router.Put("/setMaintenance", setTriggerMaintenance)
router.With(middleware.DateRange("-1hour", "now")).Get("/render", renderTrigger)
router.With(middleware.DateRange("-1hour", "now")).With(middleware.TargetName("t1")).Get("/render", renderTrigger)
}

func updateTrigger(writer http.ResponseWriter, request *http.Request) {
Expand Down
34 changes: 20 additions & 14 deletions api/handler/trigger_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,23 @@ import (
)

func renderTrigger(writer http.ResponseWriter, request *http.Request) {
sourceProvider, from, to, triggerID, fetchRealtimeData, err := getEvaluationParameters(request)
sourceProvider, targetName, from, to, triggerID, fetchRealtimeData, err := getEvaluationParameters(request)
if err != nil {
render.Render(writer, request, api.ErrorInvalidRequest(err))
return
}
metricsData, trigger, err := evaluateTriggerMetrics(sourceProvider, from, to, triggerID, fetchRealtimeData)
metricsData, trigger, err := evaluateTargetMetrics(sourceProvider, from, to, triggerID, fetchRealtimeData)
if err != nil {
render.Render(writer, request, api.ErrorInternalServer(err))
return
}
renderable, err := buildRenderable(request, trigger, metricsData)

targetMetrics, ok := metricsData[targetName]
if !ok {
render.Render(writer, request, api.ErrorNotFound(fmt.Sprintf("Cannot find target %s", targetName)))
}

renderable, err := buildRenderable(request, trigger, targetMetrics, targetName)
if err != nil {
render.Render(writer, request, api.ErrorInternalServer(err))
return
Expand All @@ -40,43 +46,43 @@ func renderTrigger(writer http.ResponseWriter, request *http.Request) {
}
}

func getEvaluationParameters(request *http.Request) (sourceProvider *metricSource.SourceProvider, from int64, to int64, triggerID string, fetchRealtimeData bool, err error) {
func getEvaluationParameters(request *http.Request) (sourceProvider *metricSource.SourceProvider, targetName string, from int64, to int64, triggerID string, fetchRealtimeData bool, err error) {
sourceProvider = middleware.GetTriggerTargetsSourceProvider(request)
targetName = middleware.GetTargetName(request)
triggerID = middleware.GetTriggerID(request)
fromStr := middleware.GetFromStr(request)
toStr := middleware.GetToStr(request)
from = date.DateParamToEpoch(fromStr, "UTC", 0, time.UTC)

if from == 0 {
return sourceProvider, 0, 0, "", false, fmt.Errorf("can not parse from: %s", fromStr)
return sourceProvider, "", 0, 0, "", false, fmt.Errorf("can not parse from: %s", fromStr)
}
from -= from % 60
to = date.DateParamToEpoch(toStr, "UTC", 0, time.UTC)
if to == 0 {
return sourceProvider, 0, 0, "", false, fmt.Errorf("can not parse to: %s", fromStr)
return sourceProvider, "", 0, 0, "", false, fmt.Errorf("can not parse to: %s", fromStr)
}
realtime := request.URL.Query().Get("realtime")
if realtime == "" {
return
}
fetchRealtimeData, err = strconv.ParseBool(realtime)
if err != nil {
return sourceProvider, 0, 0, "", false, fmt.Errorf("invalid realtime param: %s", err.Error())
return sourceProvider, "", 0, 0, "", false, fmt.Errorf("invalid realtime param: %s", err.Error())
}
return
}

func evaluateTriggerMetrics(metricSourceProvider *metricSource.SourceProvider, from, to int64, triggerID string, fetchRealtimeData bool) ([]*metricSource.MetricData, *moira.Trigger, error) {
func evaluateTargetMetrics(metricSourceProvider *metricSource.SourceProvider, from, to int64, triggerID string, fetchRealtimeData bool) (map[string][]metricSource.MetricData, *moira.Trigger, error) {
tts, trigger, err := controller.GetTriggerEvaluationResult(database, metricSourceProvider, from, to, triggerID, fetchRealtimeData)
if err != nil {
return nil, trigger, err
}
var metricsData = make([]*metricSource.MetricData, 0, len(tts.Main)+len(tts.Additional))
metricsData = append(metricsData, tts.Main...)
metricsData = append(metricsData, tts.Additional...)
return metricsData, trigger, err

return tts, trigger, err
}

func buildRenderable(request *http.Request, trigger *moira.Trigger, metricsData []*metricSource.MetricData) (*chart.Chart, error) {
func buildRenderable(request *http.Request, trigger *moira.Trigger, metricsData []metricSource.MetricData, targetName string) (*chart.Chart, error) {
timezone := request.URL.Query().Get("timezone")
location, err := time.LoadLocation(timezone)
if err != nil {
Expand All @@ -87,7 +93,7 @@ func buildRenderable(request *http.Request, trigger *moira.Trigger, metricsData
if err != nil {
return nil, fmt.Errorf("can not initialize plot theme %s", err.Error())
}
renderable, err := plotTemplate.GetRenderable(trigger, metricsData)
renderable, err := plotTemplate.GetRenderable(targetName, trigger, metricsData)
if err != nil {
return nil, err
}
Expand Down
14 changes: 14 additions & 0 deletions api/middleware/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,17 @@ func DateRange(defaultFrom, defaultTo string) func(next http.Handler) http.Handl
})
}
}

// TargetName is a function that gets target name value from query string and places it in context. If query does not have value sets given value.
func TargetName(defaultTargetName string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
targetName := request.URL.Query().Get("target")
if targetName == "" {
targetName = defaultTargetName
}
ctx := context.WithValue(request.Context(), targetNameKey, targetName)
next.ServeHTTP(writer, request.WithContext(ctx))
})
}
}
6 changes: 6 additions & 0 deletions api/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var (
loginKey ContextKey = "login"
timeSeriesNamesKey ContextKey = "timeSeriesNames"
metricSourceProvider ContextKey = "metricSourceProvider"
targetNameKey ContextKey = "target"
)

// GetDatabase gets moira.Database realization from request context
Expand Down Expand Up @@ -114,3 +115,8 @@ func GetTimeSeriesNames(request *http.Request) map[string]bool {
func GetTriggerTargetsSourceProvider(request *http.Request) *metricSource.SourceProvider {
return request.Context().Value(metricSourceProvider).(*metricSource.SourceProvider)
}

// GetTargetName gets target name
func GetTargetName(request *http.Request) string {
return request.Context().Value(targetNameKey).(string)
}

0 comments on commit 40f7582

Please sign in to comment.