Skip to content

Commit

Permalink
feat(index): more information about checks and worker groups
Browse files Browse the repository at this point in the history
  • Loading branch information
mentos1386 committed May 27, 2024
1 parent 2a0447b commit 3e88327
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 115 deletions.
5 changes: 5 additions & 0 deletions internal/server/activities/targets_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/dop251/goja"
"github.com/mentos1386/zdravko/database/models"
"github.com/mentos1386/zdravko/internal/server/services"
"github.com/mentos1386/zdravko/internal/temporal"
"github.com/mentos1386/zdravko/pkg/script"
Expand All @@ -25,6 +26,10 @@ func (a *Activities) TargetsFilter(ctx context.Context, param temporal.ActivityT
}

for _, target := range allTargets {
if target.State == models.TargetStatePaused {
continue
}

vm := goja.New()
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())

Expand Down
157 changes: 117 additions & 40 deletions internal/server/handlers/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handlers
import (
"context"
"net/http"
"sort"
"time"

"github.com/labstack/echo/v4"
Expand All @@ -24,17 +25,31 @@ type Target struct {
Visibility models.TargetVisibility
Group string
Status models.TargetStatus
History *History
History []*HistoryItem
Uptime float64
}

type History struct {
List []*HistoryItem
Uptime float64
}

type HistoryItem struct {
Status models.TargetStatus
Date time.Time
Status models.TargetStatus
StatusCounts map[models.TargetStatus]int
Counts int
Date time.Time
Checks []*HistoryItemCheck
SuccessRate float64
}

type History struct {
List []HistoryItem
Uptime float64
type HistoryItemCheck struct {
Name string
WorkerGroupName string
Status models.TargetStatus
StatusCounts map[models.TargetStatus]int
Counts int
SuccessRate float64
}

type TargetsAndStatus struct {
Expand All @@ -47,20 +62,31 @@ func getDateString(date time.Time) string {
}

func getHistory(history []*services.TargetHistory, period time.Duration, buckets int) *History {
historyMap := map[string]models.TargetStatus{}
historyMap := map[string]*HistoryItem{}
numOfSuccess := 0.0
numTotal := 0.0

mapKeys := make([]string, buckets)

for i := 0; i < buckets; i++ {
dateString := getDateString(time.Now().Add(period * time.Duration(-i)).Truncate(period))
historyMap[dateString] = models.TargetStatusUnknown
date := time.Now().Add(period * time.Duration(-i)).Truncate(period)
dateString := getDateString(date)
mapKeys[i] = dateString

historyMap[dateString] = &HistoryItem{
Status: models.TargetStatusUnknown,
StatusCounts: map[models.TargetStatus]int{},
Date: date,
Checks: []*HistoryItemCheck{},
SuccessRate: 0.0,
}
}

for _, _history := range history {
dateString := getDateString(_history.CreatedAt.Time.Truncate(period))

// Skip if not part of the "buckets"
if _, ok := historyMap[dateString]; !ok {
entry, ok := historyMap[dateString]
if !ok {
continue
}

Expand All @@ -69,31 +95,71 @@ func getHistory(history []*services.TargetHistory, period time.Duration, buckets
numOfSuccess++
}

// skip if it is already set to failure
if historyMap[dateString] == models.TargetStatusFailure {
continue
if entry.Status == models.TargetStatusUnknown {
entry.Status = _history.Status
}

// FIXME: This is wrong! As we can have multiple targets in dateString.
// We should look at only the newest one.
historyMap[dateString] = _history.Status
}
// If not yet failure, and failing check. Mark as failing.
if _history.Status == models.TargetStatusFailure && entry.Status != models.TargetStatusFailure {
entry.Status = models.TargetStatusFailure
}

historyItems := make([]HistoryItem, buckets)
for i := 0; i < buckets; i++ {
date := time.Now().Add(period * time.Duration(-buckets+i+1)).Truncate(period)
datestring := getDateString(date)
historyItems[i] = HistoryItem{
Status: historyMap[datestring],
Date: date,
entry.StatusCounts[_history.Status]++
entry.Counts++
entry.SuccessRate = 100.0 * float64(entry.StatusCounts[models.TargetStatusSuccess]) / float64(entry.Counts)

foundCheck := false
for _, check := range entry.Checks {
if check.Name == _history.CheckName && check.WorkerGroupName == _history.WorkerGroupName {
foundCheck = true

check.StatusCounts[_history.Status]++
check.Counts++
check.SuccessRate = 100.0 * float64(check.StatusCounts[models.TargetStatusSuccess]) / float64(check.Counts)

if check.Status != models.TargetStatusFailure && _history.Status == models.TargetStatusFailure {
check.Status = models.TargetStatusFailure
}
}
}

if !foundCheck {
successRate := 0.0
if _history.Status == models.TargetStatusSuccess {
successRate = 100.0
}
entry.Checks = append(entry.Checks, &HistoryItemCheck{
Name: _history.CheckName,
WorkerGroupName: _history.WorkerGroupName,
Status: _history.Status,
StatusCounts: map[models.TargetStatus]int{
_history.Status: 1,
},
Counts: 1,
SuccessRate: successRate,
})

sort.Slice(entry.Checks, func(i, j int) bool {
byName := entry.Checks[i].Name < entry.Checks[j].Name
byWorkerGroupName := entry.Checks[i].WorkerGroupName < entry.Checks[j].WorkerGroupName
return byName || (entry.Checks[i].Name == entry.Checks[j].Name && byWorkerGroupName)
})
}

historyMap[dateString] = entry
}

uptime := 0.0
if numTotal > 0 {
uptime = 100.0 * numOfSuccess / numTotal
}

historyItems := make([]*HistoryItem, 0, len(historyMap))
for i := buckets - 1; i >= 0; i-- {
key := mapKeys[i]
historyItems = append(historyItems, historyMap[key])
}

return &History{
List: historyItems,
Uptime: uptime,
Expand All @@ -107,30 +173,40 @@ func (h *BaseHandler) Index(c echo.Context) error {
return err
}

timeRange := c.QueryParam("time-range")
if timeRange != "48hours" && timeRange != "90days" && timeRange != "90minutes" {
timeRange = "90days"
timeRangeQuery := c.QueryParam("time-range")
if timeRangeQuery != "48hours" && timeRangeQuery != "90days" && timeRangeQuery != "90minutes" {
timeRangeQuery = "90days"
}

var timeBuckets int
var timeInterval time.Duration
var timeRange services.TargetHistoryDateRange
switch timeRangeQuery {
case "90days":
timeRange = services.TargetHistoryDateRange90Days
timeInterval = time.Hour * 24
timeBuckets = 90
case "48hours":
timeRange = services.TargetHistoryDateRange48Hours
timeInterval = time.Hour
timeBuckets = 48
case "90minutes":
timeRange = services.TargetHistoryDateRange90Minutes
timeInterval = time.Minute
timeBuckets = 90
}

overallStatus := models.TargetStatusUnknown
statusByGroup := make(map[string]models.TargetStatus)

targetsWithHistory := make([]*Target, len(targets))
for i, target := range targets {
history, err := services.GetTargetHistoryForTarget(ctx, h.db, target.Id)
history, err := services.GetTargetHistoryForTarget(ctx, h.db, target.Id, timeRange)
if err != nil {
return err
}

var historyResult *History
switch timeRange {
case "48hours":
historyResult = getHistory(history, time.Hour, 48)
case "90days":
historyResult = getHistory(history, time.Hour*24, 90)
case "90minutes":
historyResult = getHistory(history, time.Minute, 90)
}
historyResult := getHistory(history, timeInterval, timeBuckets)

if statusByGroup[target.Group] == "" {
statusByGroup[target.Group] = models.TargetStatusUnknown
Expand All @@ -155,7 +231,8 @@ func (h *BaseHandler) Index(c echo.Context) error {
Visibility: target.Visibility,
Group: target.Group,
Status: status.Status,
History: historyResult,
History: historyResult.List,
Uptime: historyResult.Uptime,
}
}

Expand All @@ -175,7 +252,7 @@ func (h *BaseHandler) Index(c echo.Context) error {
Navbar: Pages,
},
Targets: targetsByGroup,
TimeRange: timeRange,
TimeRange: timeRangeQuery,
Status: overallStatus,
})
}
2 changes: 1 addition & 1 deletion internal/server/handlers/settings_targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (h *BaseHandler) SettingsTargetsDescribeGET(c echo.Context) error {
return err
}

history, err := services.GetTargetHistoryForTarget(context.Background(), h.db, slug)
history, err := services.GetTargetHistoryForTarget(context.Background(), h.db, slug, services.TargetHistoryDateRange90Minutes)
if err != nil {
return err
}
Expand Down
51 changes: 29 additions & 22 deletions internal/server/services/targets_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package services

import (
"context"
"fmt"

"github.com/jmoiron/sqlx"
"github.com/mentos1386/zdravko/database/models"
Expand All @@ -14,28 +15,30 @@ type TargetHistory struct {
CheckName string `db:"check_name"`
}

func GetLastNTargetHistory(ctx context.Context, db *sqlx.DB, n int) ([]*TargetHistory, error) {
var targetHistory []*TargetHistory
err := db.SelectContext(ctx, &targetHistory, `
SELECT
th.*,
t.name AS target_name,
wg.name AS worker_group_name,
c.name AS check_name
FROM target_histories th
LEFT JOIN targets t ON th.target_id = t.id
LEFT JOIN worker_groups wg ON th.worker_group_id = wg.id
LEFT JOIN checks c ON th.check_id = c.id
WHERE th.target_id = $1
ORDER BY th.created_at DESC
LIMIT $1
`, n)
return targetHistory, err
}
type TargetHistoryDateRange string

const (
TargetHistoryDateRange90Days TargetHistoryDateRange = "90_DAYS"
TargetHistoryDateRange48Hours TargetHistoryDateRange = "48_HOURS"
TargetHistoryDateRange90Minutes TargetHistoryDateRange = "90_MINUTES"
)

func GetTargetHistoryForTarget(ctx context.Context, db *sqlx.DB, targetId string, dateRange TargetHistoryDateRange) ([]*TargetHistory, error) {
dateRangeFilter := ""
switch dateRange {
case TargetHistoryDateRange90Days:
dateRangeFilter = "AND strftime('%Y-%m-%dT%H:%M:%fZ', th.created_at) >= datetime('now', 'localtime', '-90 days')"
case TargetHistoryDateRange48Hours:
dateRangeFilter = "AND strftime('%Y-%m-%dT%H:%M:%fZ', th.created_at) >= datetime('now', 'localtime', '-48 hours')"
case TargetHistoryDateRange90Minutes:
dateRangeFilter = "AND strftime('%Y-%m-%dT%H:%M:%fZ', th.created_at) >= datetime('now', 'localtime', '-90 minutes')"
}

func GetTargetHistoryForTarget(ctx context.Context, db *sqlx.DB, targetId string) ([]*TargetHistory, error) {
var targetHistory []*TargetHistory
err := db.SelectContext(ctx, &targetHistory, `
err := db.SelectContext(
ctx,
&targetHistory,
fmt.Sprintf(`
SELECT
th.*,
t.name AS target_name,
Expand All @@ -45,9 +48,13 @@ func GetTargetHistoryForTarget(ctx context.Context, db *sqlx.DB, targetId string
LEFT JOIN targets t ON th.target_id = t.id
LEFT JOIN worker_groups wg ON th.worker_group_id = wg.id
LEFT JOIN checks c ON th.check_id = c.id
WHERE th.target_id = $1
WHERE
th.target_id = $1
%s
ORDER BY th.created_at DESC
`, targetId)
`, dateRangeFilter),
targetId,
)
return targetHistory, err
}

Expand Down
12 changes: 4 additions & 8 deletions web/static/css/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -730,10 +730,6 @@ video {
height: 5rem;
}

.h-3 {
height: 0.75rem;
}

.h-4 {
height: 1rem;
}
Expand Down Expand Up @@ -768,10 +764,6 @@ video {
width: 5rem;
}

.w-3 {
width: 0.75rem;
}

.w-4 {
width: 1rem;
}
Expand Down Expand Up @@ -845,6 +837,10 @@ video {
justify-content: center;
}

.justify-between {
justify-content: space-between;
}

.gap-1 {
gap: 0.25rem;
}
Expand Down
Loading

0 comments on commit 3e88327

Please sign in to comment.