mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
234 lines
5.9 KiB
Go
234 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"vbom.ml/util/sortorder"
|
|
|
|
"github.com/prymitive/karma/internal/alertmanager"
|
|
"github.com/prymitive/karma/internal/config"
|
|
"github.com/prymitive/karma/internal/filters"
|
|
"github.com/prymitive/karma/internal/models"
|
|
"github.com/prymitive/karma/internal/slices"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func getFiltersFromQuery(filterStrings []string) ([]filters.FilterT, bool) {
|
|
validFilters := false
|
|
matchFilters := []filters.FilterT{}
|
|
for _, filterExpression := range filterStrings {
|
|
f := filters.NewFilter(filterExpression)
|
|
if f.GetIsValid() {
|
|
validFilters = true
|
|
}
|
|
matchFilters = append(matchFilters, f)
|
|
}
|
|
return matchFilters, validFilters
|
|
}
|
|
|
|
func countLabel(countStore map[string]map[string]int, key string, val string) {
|
|
if _, found := countStore[key]; !found {
|
|
countStore[key] = make(map[string]int)
|
|
}
|
|
if _, found := countStore[key][val]; found {
|
|
countStore[key][val]++
|
|
} else {
|
|
countStore[key][val] = 1
|
|
}
|
|
}
|
|
|
|
func countersToLabelStats(counters map[string]map[string]int) models.LabelNameStatsList {
|
|
data := models.LabelNameStatsList{}
|
|
|
|
for name, valueMap := range counters {
|
|
nameStats := models.LabelNameStats{
|
|
Name: name,
|
|
Values: models.LabelValueStatsList{},
|
|
}
|
|
|
|
for value, hits := range valueMap {
|
|
nameStats.Hits += hits
|
|
valueStats := models.LabelValueStats{
|
|
Value: value,
|
|
Raw: fmt.Sprintf("%s=%s", name, value),
|
|
Hits: hits,
|
|
}
|
|
nameStats.Values = append(nameStats.Values, valueStats)
|
|
}
|
|
|
|
// now that we have total hits we can calculate %
|
|
var totalPercent int
|
|
for i, value := range nameStats.Values {
|
|
nameStats.Values[i].Percent = int(math.Floor((float64(value.Hits) / float64(nameStats.Hits)) * 100.0))
|
|
totalPercent += nameStats.Values[i].Percent
|
|
}
|
|
sort.Sort(nameStats.Values)
|
|
for totalPercent < 100 {
|
|
for i := range nameStats.Values {
|
|
nameStats.Values[i].Percent++
|
|
totalPercent++
|
|
if totalPercent >= 100 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// now that we have all % and values are sorted we can calculate offsets
|
|
offset := 0
|
|
for i, value := range nameStats.Values {
|
|
nameStats.Values[i].Offset = offset
|
|
offset += value.Percent
|
|
}
|
|
data = append(data, nameStats)
|
|
}
|
|
|
|
sort.Sort(data)
|
|
|
|
return data
|
|
}
|
|
|
|
func getUpstreams() models.AlertmanagerAPISummary {
|
|
summary := models.AlertmanagerAPISummary{}
|
|
|
|
clusters := map[string][]string{}
|
|
upstreams := alertmanager.GetAlertmanagers()
|
|
for _, upstream := range upstreams {
|
|
members := upstream.ClusterMemberNames()
|
|
key, err := slices.StringSliceToSHA1(members)
|
|
if err != nil {
|
|
log.Errorf("slices.StringSliceToSHA1 error: %s", err)
|
|
continue
|
|
}
|
|
if _, found := clusters[key]; !found {
|
|
clusters[key] = members
|
|
}
|
|
|
|
u := models.AlertmanagerAPIStatus{
|
|
Name: upstream.Name,
|
|
URI: upstream.SanitizedURI(),
|
|
PublicURI: upstream.PublicURI(),
|
|
Error: upstream.Error(),
|
|
Version: upstream.Version(),
|
|
Cluster: upstream.ClusterID(),
|
|
ClusterMembers: members,
|
|
}
|
|
summary.Instances = append(summary.Instances, u)
|
|
|
|
summary.Counters.Total++
|
|
if u.Error == "" {
|
|
summary.Counters.Healthy++
|
|
} else {
|
|
summary.Counters.Failed++
|
|
}
|
|
}
|
|
summary.Clusters = clusters
|
|
|
|
return summary
|
|
}
|
|
|
|
func resolveLabelValue(name, value string) string {
|
|
valueReplacements, found := config.Config.Grid.Sorting.CustomValues.Labels[name]
|
|
if found {
|
|
if replacement, ok := valueReplacements[value]; ok {
|
|
return replacement
|
|
}
|
|
}
|
|
return value
|
|
}
|
|
|
|
func getGroupLabel(group *models.APIAlertGroup, label string) string {
|
|
if v, found := group.Labels[label]; found {
|
|
return resolveLabelValue(label, v)
|
|
}
|
|
if v, found := group.Shared.Labels[label]; found {
|
|
return resolveLabelValue(label, v)
|
|
}
|
|
if v, found := group.Alerts[0].Labels[label]; found {
|
|
return resolveLabelValue(label, v)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func sortByStartsAt(i, j int, groups []models.APIAlertGroup, sortReverse bool) bool {
|
|
if sortReverse {
|
|
return groups[i].LatestStartsAt.After(groups[j].LatestStartsAt)
|
|
}
|
|
return groups[i].LatestStartsAt.Before(groups[j].LatestStartsAt)
|
|
}
|
|
|
|
func sortAlertGroups(c *gin.Context, groupsMap map[string]models.APIAlertGroup) []models.APIAlertGroup {
|
|
groups := make([]models.APIAlertGroup, 0, len(groupsMap))
|
|
|
|
sortOrder, found := c.GetQuery("sortOrder")
|
|
if !found || sortOrder == "" {
|
|
sortOrder = config.Config.Grid.Sorting.Order
|
|
}
|
|
|
|
sortReverse, found := c.GetQuery("sortReverse")
|
|
if !found || (sortReverse != "0" && sortReverse != "1") {
|
|
if config.Config.Grid.Sorting.Reverse {
|
|
sortReverse = "1"
|
|
} else {
|
|
sortReverse = "0"
|
|
}
|
|
}
|
|
|
|
sortLabel, found := c.GetQuery("sortLabel")
|
|
if !found || sortLabel == "" {
|
|
sortLabel = config.Config.Grid.Sorting.Label
|
|
}
|
|
|
|
for _, g := range groupsMap {
|
|
groups = append(groups, g)
|
|
}
|
|
|
|
switch sortOrder {
|
|
case "startsAt":
|
|
sort.Slice(groups, func(i, j int) bool {
|
|
return sortByStartsAt(i, j, groups, sortReverse == "1")
|
|
})
|
|
case "label":
|
|
sort.Slice(groups, func(i, j int) bool {
|
|
vi := getGroupLabel(&groups[i], sortLabel)
|
|
vj := getGroupLabel(&groups[j], sortLabel)
|
|
if vi == "" && vj == "" {
|
|
// both groups lack this label, fallback to timestamp sort
|
|
return sortByStartsAt(i, j, groups, true)
|
|
}
|
|
|
|
if vi == "" {
|
|
// first label is missing
|
|
return sortReverse != "0"
|
|
}
|
|
if vj == "" {
|
|
// second label is missing
|
|
return sortReverse == "0"
|
|
}
|
|
if vi == vj {
|
|
// both labels are equal fallback to timestamp sort
|
|
return sortByStartsAt(i, j, groups, true)
|
|
}
|
|
// finnally return groups sorted by label
|
|
if sortReverse == "1" {
|
|
return !sortorder.NaturalLess(vi, vj)
|
|
}
|
|
return sortorder.NaturalLess(vi, vj)
|
|
})
|
|
default:
|
|
// sort alert groups so they are always returned in the same order
|
|
// use group ID which is unique and immutable
|
|
sort.Slice(groups, func(i, j int) bool {
|
|
if sortReverse == "1" {
|
|
return groups[i].ID < groups[j].ID
|
|
}
|
|
return groups[i].ID > groups[j].ID
|
|
})
|
|
}
|
|
|
|
return groups
|
|
}
|