123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- package main
- import (
- "fmt"
- "math"
- "net/http"
- "strconv"
- "strings"
- "time"
- "github.com/fatih/color"
- dto "github.com/prometheus/client_model/go"
- "github.com/prometheus/prom2json"
- log "github.com/sirupsen/logrus"
- "github.com/crowdsecurity/go-cs-lib/trace"
- "github.com/crowdsecurity/crowdsec/pkg/cwhub"
- )
- func ShowMetrics(hub *cwhub.Hub, hubItem *cwhub.Item) {
- switch hubItem.Type {
- case cwhub.PARSERS:
- metrics := GetParserMetric(csConfig.Cscli.PrometheusUrl, hubItem.Name)
- parserMetricsTable(color.Output, hubItem.Name, metrics)
- case cwhub.SCENARIOS:
- metrics := GetScenarioMetric(csConfig.Cscli.PrometheusUrl, hubItem.Name)
- scenarioMetricsTable(color.Output, hubItem.Name, metrics)
- case cwhub.COLLECTIONS:
- for _, item := range hubItem.Parsers {
- metrics := GetParserMetric(csConfig.Cscli.PrometheusUrl, item)
- parserMetricsTable(color.Output, item, metrics)
- }
- for _, item := range hubItem.Scenarios {
- metrics := GetScenarioMetric(csConfig.Cscli.PrometheusUrl, item)
- scenarioMetricsTable(color.Output, item, metrics)
- }
- for _, item := range hubItem.Collections {
- hubItem = hub.GetItem(cwhub.COLLECTIONS, item)
- if hubItem == nil {
- log.Fatalf("unable to retrieve item '%s' from collection '%s'", item, hubItem.Name)
- }
- ShowMetrics(hub, hubItem)
- }
- default:
- log.Errorf("item of type '%s' is unknown", hubItem.Type)
- }
- }
- // GetParserMetric is a complete rip from prom2json
- func GetParserMetric(url string, itemName string) map[string]map[string]int {
- stats := make(map[string]map[string]int)
- result := GetPrometheusMetric(url)
- for idx, fam := range result {
- if !strings.HasPrefix(fam.Name, "cs_") {
- continue
- }
- log.Tracef("round %d", idx)
- for _, m := range fam.Metrics {
- metric, ok := m.(prom2json.Metric)
- if !ok {
- log.Debugf("failed to convert metric to prom2json.Metric")
- continue
- }
- name, ok := metric.Labels["name"]
- if !ok {
- log.Debugf("no name in Metric %v", metric.Labels)
- }
- if name != itemName {
- continue
- }
- source, ok := metric.Labels["source"]
- if !ok {
- log.Debugf("no source in Metric %v", metric.Labels)
- } else {
- if srctype, ok := metric.Labels["type"]; ok {
- source = srctype + ":" + source
- }
- }
- value := m.(prom2json.Metric).Value
- fval, err := strconv.ParseFloat(value, 32)
- if err != nil {
- log.Errorf("Unexpected int value %s : %s", value, err)
- continue
- }
- ival := int(fval)
- switch fam.Name {
- case "cs_reader_hits_total":
- if _, ok := stats[source]; !ok {
- stats[source] = make(map[string]int)
- stats[source]["parsed"] = 0
- stats[source]["reads"] = 0
- stats[source]["unparsed"] = 0
- stats[source]["hits"] = 0
- }
- stats[source]["reads"] += ival
- case "cs_parser_hits_ok_total":
- if _, ok := stats[source]; !ok {
- stats[source] = make(map[string]int)
- }
- stats[source]["parsed"] += ival
- case "cs_parser_hits_ko_total":
- if _, ok := stats[source]; !ok {
- stats[source] = make(map[string]int)
- }
- stats[source]["unparsed"] += ival
- case "cs_node_hits_total":
- if _, ok := stats[source]; !ok {
- stats[source] = make(map[string]int)
- }
- stats[source]["hits"] += ival
- case "cs_node_hits_ok_total":
- if _, ok := stats[source]; !ok {
- stats[source] = make(map[string]int)
- }
- stats[source]["parsed"] += ival
- case "cs_node_hits_ko_total":
- if _, ok := stats[source]; !ok {
- stats[source] = make(map[string]int)
- }
- stats[source]["unparsed"] += ival
- default:
- continue
- }
- }
- }
- return stats
- }
- func GetScenarioMetric(url string, itemName string) map[string]int {
- stats := make(map[string]int)
- stats["instantiation"] = 0
- stats["curr_count"] = 0
- stats["overflow"] = 0
- stats["pour"] = 0
- stats["underflow"] = 0
- result := GetPrometheusMetric(url)
- for idx, fam := range result {
- if !strings.HasPrefix(fam.Name, "cs_") {
- continue
- }
- log.Tracef("round %d", idx)
- for _, m := range fam.Metrics {
- metric, ok := m.(prom2json.Metric)
- if !ok {
- log.Debugf("failed to convert metric to prom2json.Metric")
- continue
- }
- name, ok := metric.Labels["name"]
- if !ok {
- log.Debugf("no name in Metric %v", metric.Labels)
- }
- if name != itemName {
- continue
- }
- value := m.(prom2json.Metric).Value
- fval, err := strconv.ParseFloat(value, 32)
- if err != nil {
- log.Errorf("Unexpected int value %s : %s", value, err)
- continue
- }
- ival := int(fval)
- switch fam.Name {
- case "cs_bucket_created_total":
- stats["instantiation"] += ival
- case "cs_buckets":
- stats["curr_count"] += ival
- case "cs_bucket_overflowed_total":
- stats["overflow"] += ival
- case "cs_bucket_poured_total":
- stats["pour"] += ival
- case "cs_bucket_underflowed_total":
- stats["underflow"] += ival
- default:
- continue
- }
- }
- }
- return stats
- }
- func GetPrometheusMetric(url string) []*prom2json.Family {
- mfChan := make(chan *dto.MetricFamily, 1024)
- // Start with the DefaultTransport for sane defaults.
- transport := http.DefaultTransport.(*http.Transport).Clone()
- // Conservatively disable HTTP keep-alives as this program will only
- // ever need a single HTTP request.
- transport.DisableKeepAlives = true
- // Timeout early if the server doesn't even return the headers.
- transport.ResponseHeaderTimeout = time.Minute
- go func() {
- defer trace.CatchPanic("crowdsec/GetPrometheusMetric")
- err := prom2json.FetchMetricFamilies(url, mfChan, transport)
- if err != nil {
- log.Fatalf("failed to fetch prometheus metrics : %v", err)
- }
- }()
- result := []*prom2json.Family{}
- for mf := range mfChan {
- result = append(result, prom2json.NewFamily(mf))
- }
- log.Debugf("Finished reading prometheus output, %d entries", len(result))
- return result
- }
- type unit struct {
- value int64
- symbol string
- }
- var ranges = []unit{
- {value: 1e18, symbol: "E"},
- {value: 1e15, symbol: "P"},
- {value: 1e12, symbol: "T"},
- {value: 1e9, symbol: "G"},
- {value: 1e6, symbol: "M"},
- {value: 1e3, symbol: "k"},
- {value: 1, symbol: ""},
- }
- func formatNumber(num int) string {
- goodUnit := unit{}
- for _, u := range ranges {
- if int64(num) >= u.value {
- goodUnit = u
- break
- }
- }
- if goodUnit.value == 1 {
- return fmt.Sprintf("%d%s", num, goodUnit.symbol)
- }
- res := math.Round(float64(num)/float64(goodUnit.value)*100) / 100
- return fmt.Sprintf("%.2f%s", res, goodUnit.symbol)
- }
|