diff --git a/main.go b/main.go index bb05627..419162b 100644 --- a/main.go +++ b/main.go @@ -85,17 +85,25 @@ var ( Name: "prefix_visibility", Help: "Visibility of the prefix", }, []string{"prefix", "city"}) + bgpMetricsGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "bgp_path", + Help: "BGP metrics", + }, + []string{"rrc", "location", "asn_origin", "as_path", "community", "last_updated", "prefix", "peer", "origin", "next_hop", "latest_time"}, + ) ) +const ripestatBase = "https://stat.ripe.net" + type PrefixState struct { Prefix string State map[string]float32 Mu sync.Mutex } -func (p *PrefixState) checkState() { +func (p *PrefixState) checkVisState() { log.Trace().Str("Prefix", p.Prefix).Msg("checking prefix state") - url := "https://stat.ripe.net/data/visibility/data.json?data_overload_limit=ignore&include=peers_seeing&resource=" + p.Prefix + "&sourceapp=" + appId + url := ripestatBase + "/data/visibility/data.json?data_overload_limit=ignore&include=peers_seeing&resource=" + p.Prefix + "&sourceapp=" + appId resp, err := http.Get(url) if err != nil { log.Error().Err(err).Msg("Fetching ripestat") @@ -107,15 +115,21 @@ func (p *PrefixState) checkState() { } defer resp.Body.Close() - var ripeStatResp RIPEStatResp - json.Unmarshal(body, &ripeStatResp) + var ripeStatVisibilityResp RIPEStatVisibilityResp + json.Unmarshal(body, &ripeStatVisibilityResp) + + if statusCode := ripeStatLookingGlassResp.StatusCode; statusCode != 200 { + log.Error().Int("status code", respCode). + Str("status", ripeStatLookingGlassResp.Status). + Msg("ripestat(vis) resp status code != 200") + } ipv6 := strings.Contains(p.Prefix, ":") p.Mu.Lock() defer p.Mu.Unlock() - for _, probe := range ripeStatResp.Data.Visibilities { + for _, probe := range ripeStatVisibilityResp.Data.Visibilities { var vis float32 if ipv6 { vis = float32(len(probe.Ipv6FullTablePeersSeeing)) / @@ -130,17 +144,63 @@ func (p *PrefixState) checkState() { } } +func (p *PrefixState) checkLGState() { + log.Trace().Str("Prefix", p.Prefix).Msg("checking prefix state") + url := ripestatBase + "/data/looking-glass/data.json?resource=" + p.Prefix + "&sourceapp=" + appId + resp, err := http.Get(url) + if err != nil { + log.Error().Err(err).Msg("Fetching ripestat") + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Error().Err(err).Msg("reading ripestat resp") + } + defer resp.Body.Close() + + var ripeStatLookingGlassResp RIPEStatLookingGlassResp + json.Unmarshal(body, &ripeStatLookingGlassResp) + + if statusCode := ripeStatLookingGlassResp.StatusCode; statusCode != 200 { + log.Error().Int("status code", respCode). + Str("status", ripeStatLookingGlassResp.Status). + Msg("ripestat(lg) resp status code != 200") + } + + p.Mu.Lock() + defer p.Mu.Unlock() + + for _, rrc := range ripeStatLookingGlassResp.Data.Rrcs { + for _, peer := range rrc.Peers { + bgpMetricsGauge.WithLabelValues( + rrc.Rrc, + rrc.Location, + peer.AsnOrigin, + peer.AsPath, + peer.Community, + peer.LastUpdated, + peer.Prefix, + peer.Peer, + peer.Origin, + peer.NextHop, + peer.LatestTime, + ).Set(1) + } + } +} + func updateStates() { log.Debug().Msg("Updating Prefixes") for _, ps := range prefixStates { - go ps.checkState() + go ps.checkVisState() + go ps.checkLGState() } } func init() { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) - zerolog.SetGlobalLevel(zerolog.DebugLevel) + zerolog.SetGlobalLevel(zerolog.InfoLevel) flag.StringVar(&appId, "appid", "exporter", "provide a unique identifier to every data call") flag.IntVar(&port, "port", 2112, "port") diff --git a/ripeStruct.go b/ripeStruct.go index 931f3d9..231a636 100644 --- a/ripeStruct.go +++ b/ripeStruct.go @@ -1,6 +1,6 @@ package main -type RIPEStatResp struct { +type RIPEStatVisibilityResp struct { Messages [][]string `json:"messages"` SeeAlso []interface{} `json:"see_also"` Version string `json:"version"` @@ -48,3 +48,43 @@ type RIPEStatResp struct { StatusCode int `json:"status_code"` Time string `json:"time"` } + +type RIPEStatLookingGlassResp struct { + Messages []interface{} `json:"messages"` + SeeAlso []interface{} `json:"see_also"` + Version string `json:"version"` + DataCallName string `json:"data_call_name"` + DataCallStatus string `json:"data_call_status"` + Cached bool `json:"cached"` + Data struct { + Rrcs []struct { + Rrc string `json:"rrc"` + Location string `json:"location"` + Peers []struct { + AsnOrigin string `json:"asn_origin"` + AsPath string `json:"as_path"` + Community string `json:"community"` + LastUpdated string `json:"last_updated"` + Prefix string `json:"prefix"` + Peer string `json:"peer"` + Origin string `json:"origin"` + NextHop string `json:"next_hop"` + LatestTime string `json:"latest_time"` + } `json:"peers"` + } `json:"rrcs"` + QueryTime string `json:"query_time"` + LatestTime string `json:"latest_time"` + Parameters struct { + Resource string `json:"resource"` + LookBackLimit int `json:"look_back_limit"` + Cache interface{} `json:"cache"` + } `json:"parameters"` + } `json:"data"` + QueryID string `json:"query_id"` + ProcessTime int `json:"process_time"` + ServerID string `json:"server_id"` + BuildVersion string `json:"build_version"` + Status string `json:"status"` + StatusCode int `json:"status_code"` + Time string `json:"time"` +}