diff --git a/notifier/notifier.go b/notifier/notifier.go index af557993371..e60cc487de5 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -22,6 +22,7 @@ import ( "net/http" "net/url" "path" + "strings" "sync" "time" @@ -411,7 +412,6 @@ func (n *Manager) setMore() { } } -// Alertmanagers returns a slice of Alertmanager URLs. func (n *Manager) Alertmanagers() []*url.URL { n.mtx.RLock() amSets := n.alertmanagers @@ -610,7 +610,10 @@ type alertmanager interface { url() *url.URL } -type alertmanagerLabels struct{ labels.Labels } +type alertmanagerLabels struct { + labels.Labels + discoveredLabels labels.Labels +} const pathLabel = "__alerts_path__" @@ -622,6 +625,62 @@ func (a alertmanagerLabels) url() *url.URL { } } +type Target struct { + discoveredLabels labels.Labels + labels labels.Labels +} + +func (a Target) Labels() labels.Labels { + lset := labels.Labels{} + for _, l := range a.labels { + if !strings.HasPrefix(l.Name, model.ReservedLabelPrefix) { + lset = append(lset, l) + } + } + url := url.URL{ + Scheme: a.labels.Get(model.SchemeLabel), + Host: a.labels.Get(model.AddressLabel), + Path: a.labels.Get(pathLabel), + } + instance := url.String() + if instance != "" { + lset = append(lset, labels.Label{Name: model.InstanceLabel, Value: instance}) + } + return lset +} + +func (a Target) DiscoveredLabels() labels.Labels { + lset := labels.Labels{} + for _, l := range a.discoveredLabels { + lset = append(lset, l) + } + return lset +} + +func (n *Manager) TargetsAll() map[string][]Target { + n.mtx.Lock() + defer n.mtx.Unlock() + targets := make(map[string][]Target, len(n.alertmanagers)) + for key, am := range n.alertmanagers { + if am.droppedAms == nil || len(am.droppedAms) == 0 { + tars := make([]Target, 0, len(am.ams)) + for _, a := range am.ams { + alb := a.(alertmanagerLabels) + tars = append(tars, Target{labels: alb.Labels, discoveredLabels: alb.discoveredLabels}) + } + targets[key] = tars + continue + } + tars := make([]Target, 0, len(am.droppedAms)) + for _, a := range am.droppedAms { + alb := a.(alertmanagerLabels) + tars = append(tars, Target{labels: alb.Labels, discoveredLabels: alb.discoveredLabels}) + } + targets[key] = tars + } + return targets +} + // alertmanagerSet contains a set of Alertmanagers discovered via a group of service // discovery definitions that have a common configuration on how alerts should be sent. type alertmanagerSet struct { @@ -732,7 +791,7 @@ func AlertmanagerFromGroup(tg *targetgroup.Group, cfg *config.AlertmanagerConfig preRelabel := lb.Labels() keep := relabel.ProcessBuilder(lb, cfg.RelabelConfigs...) if !keep { - droppedAlertManagers = append(droppedAlertManagers, alertmanagerLabels{preRelabel}) + droppedAlertManagers = append(droppedAlertManagers, alertmanagerLabels{Labels: preRelabel, discoveredLabels: preRelabel}) continue } @@ -741,7 +800,7 @@ func AlertmanagerFromGroup(tg *targetgroup.Group, cfg *config.AlertmanagerConfig return nil, nil, err } - res = append(res, alertmanagerLabels{lb.Labels()}) + res = append(res, alertmanagerLabels{Labels: lb.Labels(), discoveredLabels: preRelabel}) } return res, droppedAlertManagers, nil } diff --git a/notifier/notifier_test.go b/notifier/notifier_test.go index 66ee45c6e88..71a742ed9e0 100644 --- a/notifier/notifier_test.go +++ b/notifier/notifier_test.go @@ -548,7 +548,8 @@ alerting: } n.reload(tgs) - res := n.DroppedAlertmanagers()[0].String() + drops := n.DroppedAlertmanagers() + res := drops[0].String() require.Equal(t, res, tt.out) } @@ -569,6 +570,14 @@ func makeInputTargetGroup() *targetgroup.Group { } } +func TestTargetLabels(t *testing.T) { + label := labels.FromStrings("alertname", "test") + discoveredLabels := labels.FromStrings("alertname", "test", "a", "b") + target := Target{labels: label, discoveredLabels: discoveredLabels} + require.Equal(t, target.Labels(), label) + require.Equal(t, target.DiscoveredLabels(), discoveredLabels) +} + func TestLabelsToOpenAPILabelSet(t *testing.T) { require.Equal(t, models.LabelSet{"aaa": "111", "bbb": "222"}, labelsToOpenAPILabelSet(labels.FromStrings("aaa", "111", "bbb", "222"))) } diff --git a/web/api/v1/api.go b/web/api/v1/api.go index 1a54f23a61f..f8aeb637f8b 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -43,6 +43,7 @@ import ( "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/textparse" "github.com/prometheus/prometheus/model/timestamp" + "github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/rules" @@ -108,6 +109,7 @@ type TargetRetriever interface { type AlertmanagerRetriever interface { Alertmanagers() []*url.URL DroppedAlertmanagers() []*url.URL + TargetsAll() map[string][]notifier.Target } // RulesRetriever provides a list of active rules and alerts. @@ -381,6 +383,7 @@ func (api *API) Register(r *route.Router) { r.Get("/targets", wrap(api.targets)) r.Get("/targets/metadata", wrap(api.targetMetadata)) r.Get("/alertmanagers", wrapAgent(api.alertmanagers)) + r.Get("/servicediscovery", wrapAgent(api.servicediscovery)) r.Get("/metadata", wrap(api.metricMetadata)) @@ -1134,6 +1137,69 @@ func (api *API) targetMetadata(r *http.Request) apiFuncResult { return apiFuncResult{res, nil, nil, nil} } +func (api *API) servicediscovery(r *http.Request) apiFuncResult { + scrapeData := func() TargetDiscovery { + var index []string + targets := api.targetRetriever(r.Context()).TargetsActive() + for job := range targets { + index = append(index, job) + } + sort.Strings(index) + res := TargetDiscovery{} + res.ActiveTargets = make([]*Target, 0) + res.DroppedTargets = make([]*DroppedTarget, 0) + for _, job := range index { + for _, target := range targets[job] { + if target.Labels().Len() == 0 { + res.DroppedTargets = append(res.DroppedTargets, &DroppedTarget{ + DiscoveredLabels: target.DiscoveredLabels().Map(), + }) + } else { + res.ActiveTargets = append(res.ActiveTargets, &Target{ + DiscoveredLabels: target.DiscoveredLabels().Map(), + Labels: target.Labels().Map(), + ScrapePool: job, + }) + } + } + } + return res + } + + alertManagerData := func() TargetDiscovery { + var index []string + targets := api.alertmanagerRetriever(r.Context()).TargetsAll() + for job := range targets { + index = append(index, job) + } + sort.Strings(index) + res := TargetDiscovery{} + res.ActiveTargets = make([]*Target, 0) + res.DroppedTargets = make([]*DroppedTarget, 0) + for _, job := range index { + for _, target := range targets[job] { + if target.Labels().Len() == 0 { + res.DroppedTargets = append(res.DroppedTargets, &DroppedTarget{ + DiscoveredLabels: target.DiscoveredLabels().Map(), + }) + } else { + res.ActiveTargets = append(res.ActiveTargets, &Target{ + DiscoveredLabels: target.DiscoveredLabels().Map(), + Labels: target.Labels().Map(), + ScrapePool: job, + }) + } + } + } + return res + } + serviceDiscoveryData := map[string]TargetDiscovery{ + "scrape": scrapeData(), + "alertManager": alertManagerData(), + } + return apiFuncResult{serviceDiscoveryData, nil, nil, nil} +} + type metricMetadata struct { Target labels.Labels `json:"target"` Metric string `json:"metric,omitempty"` diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 475b4bab54f..81a9ced0bf8 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -29,9 +29,6 @@ import ( "testing" "time" - "github.com/prometheus/prometheus/prompb" - "github.com/prometheus/prometheus/util/stats" - "github.com/go-kit/log" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" @@ -46,6 +43,8 @@ import ( "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/textparse" "github.com/prometheus/prometheus/model/timestamp" + "github.com/prometheus/prometheus/notifier" + "github.com/prometheus/prometheus/prompb" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/rules" @@ -53,6 +52,7 @@ import ( "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage/remote" "github.com/prometheus/prometheus/tsdb" + "github.com/prometheus/prometheus/util/stats" "github.com/prometheus/prometheus/util/teststorage" ) @@ -204,6 +204,10 @@ func (t testAlertmanagerRetriever) DroppedAlertmanagers() []*url.URL { } } +func (t testAlertmanagerRetriever) TargetsAll() map[string][]notifier.Target { + return nil +} + func (t testAlertmanagerRetriever) toFactory() func(context.Context) AlertmanagerRetriever { return func(context.Context) AlertmanagerRetriever { return t } } diff --git a/web/api/v1/errors_test.go b/web/api/v1/errors_test.go index 4673af201e9..b7493f097a4 100644 --- a/web/api/v1/errors_test.go +++ b/web/api/v1/errors_test.go @@ -32,6 +32,7 @@ import ( "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/rules" "github.com/prometheus/prometheus/scrape" @@ -238,6 +239,10 @@ func (DummyTargetRetriever) TargetsDroppedCounts() map[string]int { // DummyAlertmanagerRetriever implements AlertmanagerRetriever. type DummyAlertmanagerRetriever struct{} +func (r DummyAlertmanagerRetriever) TargetsAll() map[string][]notifier.Target { + return nil +} + // Alertmanagers implements AlertmanagerRetriever. func (DummyAlertmanagerRetriever) Alertmanagers() []*url.URL { return nil } diff --git a/web/ui/react-app/src/pages/graph/SeriesName.tsx b/web/ui/react-app/src/pages/graph/SeriesName.tsx index 59abfa59dcd..f013946dd7b 100644 --- a/web/ui/react-app/src/pages/graph/SeriesName.tsx +++ b/web/ui/react-app/src/pages/graph/SeriesName.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react'; +import React, { FC } from 'react'; import { useToastContext } from '../../contexts/ToastContext'; import { metricToSeriesName } from '../../utils'; diff --git a/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx b/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx index 79d88fbe4f5..df8c56e9f08 100644 --- a/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx +++ b/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx @@ -11,6 +11,10 @@ import { KVSearch } from '@nexucis/kvsearch'; import { Container } from 'reactstrap'; import SearchBar from '../../components/SearchBar'; +interface ServiceDiscoveryMap { + scrape: ServiceMap; + alertManager: ServiceMap; +} interface ServiceMap { activeTargets: Target[]; droppedTargets: DroppedTarget[]; @@ -33,10 +37,7 @@ const droppedTargetKVSearch = new KVSearch({ indexedKeys: ['discoveredLabels', ['discoveredLabels', /.*/]], }); -export const processSummary = ( - activeTargets: Target[], - droppedTargetCounts: Record -): Record => { +export const processSummary = (activeTargets: Target[], droppedTargets: DroppedTarget[]) => { const targets: Record = {}; // Get targets of each type along with the total and active end points @@ -51,21 +52,20 @@ export const processSummary = ( targets[name].total++; targets[name].active++; } - for (const name in targets) { + for (const target of droppedTargets) { + const { job: name } = target.discoveredLabels; if (!targets[name]) { targets[name] = { - total: droppedTargetCounts[name], + total: 0, active: 0, }; - } else { - targets[name].total += droppedTargetCounts[name]; } } return targets; }; -export const processTargets = (activeTargets: Target[], droppedTargets: DroppedTarget[]): Record => { +export const processTargets = (activeTargets: Target[], droppedTargets: DroppedTarget[]) => { const labels: Record = {}; for (const target of activeTargets) { @@ -95,37 +95,38 @@ export const processTargets = (activeTargets: Target[], droppedTargets: DroppedT return labels; }; -export const ServiceDiscoveryContent: FC = ({ activeTargets, droppedTargets, droppedTargetCounts }) => { - const [activeTargetList, setActiveTargetList] = useState(activeTargets); - const [droppedTargetList, setDroppedTargetList] = useState(droppedTargets); - const [targetList, setTargetList] = useState(processSummary(activeTargets, droppedTargetCounts)); - const [labelList, setLabelList] = useState(processTargets(activeTargets, droppedTargets)); - +export const ServiceDiscoveryContent: FC = ({ scrape, alertManager }) => { + const [activeTargetList, setActiveTargetList] = useState(scrape.activeTargets); + const [droppedTargetList, setDroppedTargetList] = useState(scrape.droppedTargets); + const [targetList, setTargetList] = useState(processSummary(scrape.activeTargets, scrape.droppedTargets)); + const [labelList, setLabelList] = useState(processTargets(scrape.activeTargets, scrape.droppedTargets)); + const alertManagerTargets = processSummary(alertManager.activeTargets, alertManager.droppedTargets); + const alertManagerLabels = processTargets(alertManager.activeTargets, alertManager.droppedTargets); const handleSearchChange = useCallback( (value: string) => { setQuerySearchFilter(value); if (value !== '') { - const activeTargetResult = activeTargetKVSearch.filter(value.trim(), activeTargets); - const droppedTargetResult = droppedTargetKVSearch.filter(value.trim(), droppedTargets); + const activeTargetResult = activeTargetKVSearch.filter(value.trim(), scrape.activeTargets); + const droppedTargetResult = droppedTargetKVSearch.filter(value.trim(), scrape.droppedTargets); setActiveTargetList(activeTargetResult.map((value) => value.original)); setDroppedTargetList(droppedTargetResult.map((value) => value.original)); } else { - setActiveTargetList(activeTargets); + setActiveTargetList(scrape.activeTargets); } }, - [activeTargets, droppedTargets] + [scrape.activeTargets, scrape.droppedTargets] ); const defaultValue = useMemo(getQuerySearchFilter, []); useEffect(() => { - setTargetList(processSummary(activeTargetList, droppedTargetCounts)); + setTargetList(processSummary(activeTargetList, scrape.droppedTargets)); setLabelList(processTargets(activeTargetList, droppedTargetList)); - }, [activeTargetList, droppedTargetList, droppedTargetCounts]); + }, [activeTargetList, droppedTargetList, scrape.droppedTargets]); return ( <> -

Service Discovery

+

Scrape Service Discovery

@@ -142,6 +143,21 @@ export const ServiceDiscoveryContent: FC = ({ activeTargets, dropped {mapObjEntries(labelList, ([k, v]) => { return ; })} + +

AlertManager Service Discovery

+ +
+ {mapObjEntries(alertManagerLabels, ([k, v]) => { + return ; + })} ); }; @@ -151,7 +167,7 @@ const ServicesWithStatusIndicator = withStatusIndicator(ServiceDiscoveryContent) const ServiceDiscovery: FC = () => { const pathPrefix = usePathPrefix(); - const { response, error, isLoading } = useFetch(`${pathPrefix}/${API_PATH}/targets`); + const { response, error, isLoading } = useFetch(`${pathPrefix}/${API_PATH}/servicediscovery`); return ( = ({ agentMode, setAnimateLogo }) => { }, []); useEffect(() => { - if (setAnimateLogo && inputText != '') { + if (setAnimateLogo && inputText !== '') { setAnimateLogo(inputText.toUpperCase() === 'QSPN'); } }, [inputText]);