Skip to content

Commit

Permalink
feat(api): add counting contacts metrics (#1002)
Browse files Browse the repository at this point in the history
  • Loading branch information
almostinf authored Mar 28, 2024
1 parent f73de55 commit 7cc7eb4
Show file tree
Hide file tree
Showing 10 changed files with 362 additions and 27 deletions.
11 changes: 8 additions & 3 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/moira-alert/moira/api/handler"
"github.com/moira-alert/moira/cmd"
"github.com/moira-alert/moira/database/redis"
"github.com/moira-alert/moira/database/stats"
"github.com/moira-alert/moira/index"
logging "github.com/moira-alert/moira/logging/zerolog_adapter"
_ "go.uber.org/automaxprocs"
Expand Down Expand Up @@ -120,9 +121,13 @@ func main() {
Msg("Failed to initialize metric sources")
}

stats := newTriggerStats(metricSourceProvider.GetClusterList(), logger, database, telemetry.Metrics)
stats.start()
defer stats.stop() //nolint
// Start stats manager
statsManager := stats.NewStatsManager(
stats.NewTriggerStats(telemetry.Metrics, database, logger, metricSourceProvider.GetClusterList()),
stats.NewContactStats(telemetry.Metrics, database, logger),
)
statsManager.Start()
defer statsManager.Stop() //nolint

webConfig := applicationConfig.Web.getSettings(len(metricSourceProvider.GetAllSources()) > 0, applicationConfig.Remotes)

Expand Down
68 changes: 68 additions & 0 deletions database/stats/contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package stats

import (
"time"

"github.com/moira-alert/moira"
"github.com/moira-alert/moira/metrics"
)

type contactStats struct {
metrics *metrics.ContactsMetrics
database moira.Database
logger moira.Logger
}

// NewContactStats creates and initializes a new contactStats object.
func NewContactStats(
metricsRegistry metrics.Registry,
database moira.Database,
logger moira.Logger,
) *contactStats {
return &contactStats{
metrics: metrics.NewContactsMetrics(metricsRegistry),
database: database,
logger: logger,
}
}

// StartReport starts reporting statistics about contacts.
func (stats *contactStats) StartReport(stop <-chan struct{}) {
checkTicker := time.NewTicker(time.Minute)
defer checkTicker.Stop()

stats.logger.Info().Msg("Start contact statistics reporter")

for {
select {
case <-stop:
stats.logger.Info().Msg("Stop contact statistics reporter")
return

case <-checkTicker.C:
stats.checkContactsCount()
}
}
}

func (stats *contactStats) checkContactsCount() {
contacts, err := stats.database.GetAllContacts()
if err != nil {
stats.logger.Warning().
Error(err).
Msg("Failed to get all contacts")
return
}

contactsCounter := make(map[string]int64)

for _, contact := range contacts {
if contact != nil {
contactsCounter[contact.Type]++
}
}

for contact, count := range contactsCounter {
stats.metrics.Mark(contact, count)
}
}
107 changes: 107 additions & 0 deletions database/stats/contact_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package stats

import (
"errors"
"testing"

"github.com/golang/mock/gomock"

"github.com/moira-alert/moira"
logging "github.com/moira-alert/moira/logging/zerolog_adapter"
"github.com/moira-alert/moira/metrics"
mock_moira_alert "github.com/moira-alert/moira/mock/moira-alert"
mock_metrics "github.com/moira-alert/moira/mock/moira-alert/metrics"
. "github.com/smartystreets/goconvey/convey"
)

const metricPrefix = "contacts"

var testContacts = []*moira.ContactData{
{
Type: "test1",
},
{
Type: "test1",
},
{
Type: "test2",
},
{
Type: "test3",
},
{
Type: "test2",
},
{
Type: "test1",
},
}

func TestNewContactsStats(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

registry := mock_metrics.NewMockRegistry(mockCtrl)
database := mock_moira_alert.NewMockDatabase(mockCtrl)
logger, _ := logging.GetLogger("Test")

Convey("Successfully created new contacts stats", t, func() {
stats := NewContactStats(registry, database, logger)

So(stats, ShouldResemble, &contactStats{
metrics: metrics.NewContactsMetrics(registry),
database: database,
logger: logger,
})
})
}

func TestCheckingContactsCount(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

registry := mock_metrics.NewMockRegistry(mockCtrl)
database := mock_moira_alert.NewMockDatabase(mockCtrl)
logger := mock_moira_alert.NewMockLogger(mockCtrl)
eventBuilder := mock_moira_alert.NewMockEventBuilder(mockCtrl)

test1Meter := mock_metrics.NewMockMeter(mockCtrl)
test2Meter := mock_metrics.NewMockMeter(mockCtrl)
test3Meter := mock_metrics.NewMockMeter(mockCtrl)

var test1ContactCount, test2ContactCount, test3ContactCount int64
var test1ContactType, test2ContactType, test3ContactType string
test1ContactCount, test1ContactType = 3, "test1"
test2ContactCount, test2ContactType = 2, "test2"
test3ContactCount, test3ContactType = 1, "test3"

getAllContactsErr := errors.New("failed to get all contacts")

Convey("Test checking contacts count", t, func() {
Convey("Successfully checking contacts count", func() {
database.EXPECT().GetAllContacts().Return(testContacts, nil).Times(1)

registry.EXPECT().NewMeter(metricPrefix, test1ContactType).Return(test1Meter).Times(1)
registry.EXPECT().NewMeter(metricPrefix, test2ContactType).Return(test2Meter).Times(1)
registry.EXPECT().NewMeter(metricPrefix, test3ContactType).Return(test3Meter).Times(1)

test1Meter.EXPECT().Mark(test1ContactCount)
test2Meter.EXPECT().Mark(test2ContactCount)
test3Meter.EXPECT().Mark(test3ContactCount)

stats := NewContactStats(registry, database, logger)
stats.checkContactsCount()
})

Convey("Get error from get all contacts", func() {
database.EXPECT().GetAllContacts().Return(nil, getAllContactsErr).Times(1)

logger.EXPECT().Warning().Return(eventBuilder).Times(1)
eventBuilder.EXPECT().Error(getAllContactsErr).Return(eventBuilder).Times(1)
eventBuilder.EXPECT().Msg("Failed to get all contacts").Times(1)

stats := NewContactStats(registry, database, logger)
stats.checkContactsCount()
})
})
}
40 changes: 40 additions & 0 deletions database/stats/stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package stats

import (
"gopkg.in/tomb.v2"
)

// StatsReporter represents an interface for objects that report statistics.
type StatsReporter interface {
StartReport(stop <-chan struct{})
}

type statsManager struct {
tomb tomb.Tomb
reporters []StatsReporter
}

// NewStatsManager creates a new statsManager instance with the given StatsReporters.
func NewStatsManager(reporters ...StatsReporter) *statsManager {
return &statsManager{
reporters: reporters,
}
}

// Start starts reporting statistics for all registered StatsReporters.
func (manager *statsManager) Start() {
for _, reporter := range manager.reporters {
reporter := reporter

manager.tomb.Go(func() error {
reporter.StartReport(manager.tomb.Dying())
return nil
})
}
}

// Stop stops all reporting activities and waits for the completion.
func (manager *statsManager) Stop() error {
manager.tomb.Kill(nil)
return manager.tomb.Wait()
}
35 changes: 35 additions & 0 deletions database/stats/stats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package stats

import (
"testing"

"github.com/golang/mock/gomock"
mock_moira_alert "github.com/moira-alert/moira/mock/moira-alert"
. "github.com/smartystreets/goconvey/convey"
)

func TestNewStatsManager(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

triggerStats := mock_moira_alert.NewMockStatsReporter(mockCtrl)
contactStats := mock_moira_alert.NewMockStatsReporter(mockCtrl)

Convey("Test new stats manager", t, func() {
Convey("Successfully create new stats manager", func() {
manager := NewStatsManager(triggerStats, contactStats)

So(manager.reporters, ShouldResemble, []StatsReporter{triggerStats, contactStats})
})

Convey("Successfully start stats manager", func() {
manager := NewStatsManager(triggerStats, contactStats)

triggerStats.EXPECT().StartReport(manager.tomb.Dying()).Times(1)
contactStats.EXPECT().StartReport(manager.tomb.Dying()).Times(1)

manager.Start()
defer manager.Stop() //nolint
})
})
}
36 changes: 16 additions & 20 deletions cmd/api/trigger_stats.go → database/stats/trigger.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
package main
package stats

import (
"time"

"github.com/moira-alert/moira"
"github.com/moira-alert/moira/metrics"
"gopkg.in/tomb.v2"
)

type triggerStats struct {
tomb tomb.Tomb
metrics *metrics.TriggersMetrics
clusters []moira.ClusterKey
database moira.Database
logger moira.Logger
clusters []moira.ClusterKey
}

func newTriggerStats(
clusters []moira.ClusterKey,
logger moira.Logger,
database moira.Database,
// NewTriggerStats creates and initializes a new triggerStats object.
func NewTriggerStats(
metricsRegistry metrics.Registry,
database moira.Database,
logger moira.Logger,
clusters []moira.ClusterKey,
) *triggerStats {
return &triggerStats{
logger: logger,
Expand All @@ -30,28 +29,25 @@ func newTriggerStats(
}
}

func (stats *triggerStats) start() {
stats.tomb.Go(stats.startCheckingTriggerCount)
}
// StartReport starts reporting statistics about triggers.
func (stats *triggerStats) StartReport(stop <-chan struct{}) {
checkTicker := time.NewTicker(time.Minute)
defer checkTicker.Stop()

stats.logger.Info().Msg("Start trigger statistics reporter")

func (stats *triggerStats) startCheckingTriggerCount() error {
checkTicker := time.NewTicker(time.Second * 60)
for {
select {
case <-stats.tomb.Dying():
return nil
case <-stop:
stats.logger.Info().Msg("Stop trigger statistics reporter")
return

case <-checkTicker.C:
stats.checkTriggerCount()
}
}
}

func (stats *triggerStats) stop() error {
stats.tomb.Kill(nil)
return stats.tomb.Wait()
}

func (stats *triggerStats) checkTriggerCount() {
triggersCount, err := stats.database.GetTriggerCount(stats.clusters)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package stats

import (
"testing"
Expand Down Expand Up @@ -30,8 +30,8 @@ func TestTriggerStatsCheckTriggerCount(t *testing.T) {
registry.EXPECT().NewMeter("triggers", moira.GraphiteRemote.String(), moira.DefaultCluster.String()).Return(graphiteRemoteMeter)
registry.EXPECT().NewMeter("triggers", moira.PrometheusRemote.String(), moira.DefaultCluster.String()).Return(prometheusRemoteMeter)

dataBase := mock_moira_alert.NewMockDatabase(mockCtrl)
dataBase.EXPECT().GetTriggerCount(gomock.Any()).Return(map[moira.ClusterKey]int64{
database := mock_moira_alert.NewMockDatabase(mockCtrl)
database.EXPECT().GetTriggerCount(gomock.Any()).Return(map[moira.ClusterKey]int64{
moira.DefaultLocalCluster: graphiteLocalCount,
moira.DefaultGraphiteRemoteCluster: graphiteRemoteCount,
moira.DefaultPrometheusRemoteCluster: prometheusRemoteCount,
Expand All @@ -47,7 +47,7 @@ func TestTriggerStatsCheckTriggerCount(t *testing.T) {
moira.DefaultGraphiteRemoteCluster,
moira.DefaultPrometheusRemoteCluster,
}
triggerStats := newTriggerStats(clusters, logger, dataBase, registry)
triggerStats := NewTriggerStats(registry, database, logger, clusters)

triggerStats.checkTriggerCount()
})
Expand Down
2 changes: 2 additions & 0 deletions generate_mocks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ mockgen -destination=mock/moira-alert/metrics/registry.go -package=mock_moira_al
mockgen -destination=mock/moira-alert/metrics/meter.go -package=mock_moira_alert github.com/moira-alert/moira/metrics Meter
mockgen -destination=mock/moira-alert/prometheus_api.go -package=mock_moira_alert github.com/moira-alert/moira/metric_source/prometheus PrometheusApi

mockgen -destination=mock/moira-alert/database_stats.go -package=mock_moira_alert github.com/moira-alert/moira/database/stats StatsReporter

git add mock/*
Loading

0 comments on commit 7cc7eb4

Please sign in to comment.