Skip to content

Commit

Permalink
feat: Improve advanced mode
Browse files Browse the repository at this point in the history
Datatype that stores metrics for check is changed to multiple datatypes
that handle different operations on data. Fetcher interface is changed to
return new implemented datatypes. Checker is changed to handle triggers
with multiple targets in new way. API, notifier are changed to fit new
CheckData struct.

Relates #428
  • Loading branch information
litleleprikon committed Jan 4, 2020
1 parent 2a1a958 commit 55b77db
Show file tree
Hide file tree
Showing 130 changed files with 3,435 additions and 1,881 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
53 changes: 21 additions & 32 deletions api/controller/trigger_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,25 @@ 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) (metricSource.FetchedMetrics, *moira.Trigger, error) {
trigger, err := dataBase.GetTrigger(triggerID)
if err != nil {
return nil, nil, err
}
triggerMetrics := metricSource.NewTriggerMetricsData()
triggerMetrics := metricSource.NewFetchedMetricsWithCapacity(0)
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...)
}

triggerMetrics.AddMetrics(i, metricData)
}
return triggerMetrics, &trigger, nil
}
Expand All @@ -57,31 +55,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
5 changes: 1 addition & 4 deletions api/dto/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,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 @@ -30,7 +30,7 @@ func TestExpressionModeMultipleTargetsWarnValue(t *testing.T) {
localSource.EXPECT().IsConfigured().Return(true, nil).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 @@ -29,7 +29,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) (metricSource.FetchedMetrics, *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 @@ -143,3 +143,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 @@ -29,6 +29,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 @@ -96,3 +97,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)
}
Loading

0 comments on commit 55b77db

Please sign in to comment.