item_metrics.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/fatih/color"
  10. dto "github.com/prometheus/client_model/go"
  11. "github.com/prometheus/prom2json"
  12. log "github.com/sirupsen/logrus"
  13. "github.com/crowdsecurity/go-cs-lib/trace"
  14. "github.com/crowdsecurity/crowdsec/pkg/cwhub"
  15. )
  16. func ShowMetrics(hub *cwhub.Hub, hubItem *cwhub.Item) {
  17. switch hubItem.Type {
  18. case cwhub.PARSERS:
  19. metrics := GetParserMetric(csConfig.Cscli.PrometheusUrl, hubItem.Name)
  20. parserMetricsTable(color.Output, hubItem.Name, metrics)
  21. case cwhub.SCENARIOS:
  22. metrics := GetScenarioMetric(csConfig.Cscli.PrometheusUrl, hubItem.Name)
  23. scenarioMetricsTable(color.Output, hubItem.Name, metrics)
  24. case cwhub.COLLECTIONS:
  25. for _, item := range hubItem.Parsers {
  26. metrics := GetParserMetric(csConfig.Cscli.PrometheusUrl, item)
  27. parserMetricsTable(color.Output, item, metrics)
  28. }
  29. for _, item := range hubItem.Scenarios {
  30. metrics := GetScenarioMetric(csConfig.Cscli.PrometheusUrl, item)
  31. scenarioMetricsTable(color.Output, item, metrics)
  32. }
  33. for _, item := range hubItem.Collections {
  34. hubItem = hub.GetItem(cwhub.COLLECTIONS, item)
  35. if hubItem == nil {
  36. log.Fatalf("unable to retrieve item '%s' from collection '%s'", item, hubItem.Name)
  37. }
  38. ShowMetrics(hub, hubItem)
  39. }
  40. default:
  41. log.Errorf("item of type '%s' is unknown", hubItem.Type)
  42. }
  43. }
  44. // GetParserMetric is a complete rip from prom2json
  45. func GetParserMetric(url string, itemName string) map[string]map[string]int {
  46. stats := make(map[string]map[string]int)
  47. result := GetPrometheusMetric(url)
  48. for idx, fam := range result {
  49. if !strings.HasPrefix(fam.Name, "cs_") {
  50. continue
  51. }
  52. log.Tracef("round %d", idx)
  53. for _, m := range fam.Metrics {
  54. metric, ok := m.(prom2json.Metric)
  55. if !ok {
  56. log.Debugf("failed to convert metric to prom2json.Metric")
  57. continue
  58. }
  59. name, ok := metric.Labels["name"]
  60. if !ok {
  61. log.Debugf("no name in Metric %v", metric.Labels)
  62. }
  63. if name != itemName {
  64. continue
  65. }
  66. source, ok := metric.Labels["source"]
  67. if !ok {
  68. log.Debugf("no source in Metric %v", metric.Labels)
  69. } else {
  70. if srctype, ok := metric.Labels["type"]; ok {
  71. source = srctype + ":" + source
  72. }
  73. }
  74. value := m.(prom2json.Metric).Value
  75. fval, err := strconv.ParseFloat(value, 32)
  76. if err != nil {
  77. log.Errorf("Unexpected int value %s : %s", value, err)
  78. continue
  79. }
  80. ival := int(fval)
  81. switch fam.Name {
  82. case "cs_reader_hits_total":
  83. if _, ok := stats[source]; !ok {
  84. stats[source] = make(map[string]int)
  85. stats[source]["parsed"] = 0
  86. stats[source]["reads"] = 0
  87. stats[source]["unparsed"] = 0
  88. stats[source]["hits"] = 0
  89. }
  90. stats[source]["reads"] += ival
  91. case "cs_parser_hits_ok_total":
  92. if _, ok := stats[source]; !ok {
  93. stats[source] = make(map[string]int)
  94. }
  95. stats[source]["parsed"] += ival
  96. case "cs_parser_hits_ko_total":
  97. if _, ok := stats[source]; !ok {
  98. stats[source] = make(map[string]int)
  99. }
  100. stats[source]["unparsed"] += ival
  101. case "cs_node_hits_total":
  102. if _, ok := stats[source]; !ok {
  103. stats[source] = make(map[string]int)
  104. }
  105. stats[source]["hits"] += ival
  106. case "cs_node_hits_ok_total":
  107. if _, ok := stats[source]; !ok {
  108. stats[source] = make(map[string]int)
  109. }
  110. stats[source]["parsed"] += ival
  111. case "cs_node_hits_ko_total":
  112. if _, ok := stats[source]; !ok {
  113. stats[source] = make(map[string]int)
  114. }
  115. stats[source]["unparsed"] += ival
  116. default:
  117. continue
  118. }
  119. }
  120. }
  121. return stats
  122. }
  123. func GetScenarioMetric(url string, itemName string) map[string]int {
  124. stats := make(map[string]int)
  125. stats["instantiation"] = 0
  126. stats["curr_count"] = 0
  127. stats["overflow"] = 0
  128. stats["pour"] = 0
  129. stats["underflow"] = 0
  130. result := GetPrometheusMetric(url)
  131. for idx, fam := range result {
  132. if !strings.HasPrefix(fam.Name, "cs_") {
  133. continue
  134. }
  135. log.Tracef("round %d", idx)
  136. for _, m := range fam.Metrics {
  137. metric, ok := m.(prom2json.Metric)
  138. if !ok {
  139. log.Debugf("failed to convert metric to prom2json.Metric")
  140. continue
  141. }
  142. name, ok := metric.Labels["name"]
  143. if !ok {
  144. log.Debugf("no name in Metric %v", metric.Labels)
  145. }
  146. if name != itemName {
  147. continue
  148. }
  149. value := m.(prom2json.Metric).Value
  150. fval, err := strconv.ParseFloat(value, 32)
  151. if err != nil {
  152. log.Errorf("Unexpected int value %s : %s", value, err)
  153. continue
  154. }
  155. ival := int(fval)
  156. switch fam.Name {
  157. case "cs_bucket_created_total":
  158. stats["instantiation"] += ival
  159. case "cs_buckets":
  160. stats["curr_count"] += ival
  161. case "cs_bucket_overflowed_total":
  162. stats["overflow"] += ival
  163. case "cs_bucket_poured_total":
  164. stats["pour"] += ival
  165. case "cs_bucket_underflowed_total":
  166. stats["underflow"] += ival
  167. default:
  168. continue
  169. }
  170. }
  171. }
  172. return stats
  173. }
  174. func GetPrometheusMetric(url string) []*prom2json.Family {
  175. mfChan := make(chan *dto.MetricFamily, 1024)
  176. // Start with the DefaultTransport for sane defaults.
  177. transport := http.DefaultTransport.(*http.Transport).Clone()
  178. // Conservatively disable HTTP keep-alives as this program will only
  179. // ever need a single HTTP request.
  180. transport.DisableKeepAlives = true
  181. // Timeout early if the server doesn't even return the headers.
  182. transport.ResponseHeaderTimeout = time.Minute
  183. go func() {
  184. defer trace.CatchPanic("crowdsec/GetPrometheusMetric")
  185. err := prom2json.FetchMetricFamilies(url, mfChan, transport)
  186. if err != nil {
  187. log.Fatalf("failed to fetch prometheus metrics : %v", err)
  188. }
  189. }()
  190. result := []*prom2json.Family{}
  191. for mf := range mfChan {
  192. result = append(result, prom2json.NewFamily(mf))
  193. }
  194. log.Debugf("Finished reading prometheus output, %d entries", len(result))
  195. return result
  196. }
  197. type unit struct {
  198. value int64
  199. symbol string
  200. }
  201. var ranges = []unit{
  202. {value: 1e18, symbol: "E"},
  203. {value: 1e15, symbol: "P"},
  204. {value: 1e12, symbol: "T"},
  205. {value: 1e9, symbol: "G"},
  206. {value: 1e6, symbol: "M"},
  207. {value: 1e3, symbol: "k"},
  208. {value: 1, symbol: ""},
  209. }
  210. func formatNumber(num int) string {
  211. goodUnit := unit{}
  212. for _, u := range ranges {
  213. if int64(num) >= u.value {
  214. goodUnit = u
  215. break
  216. }
  217. }
  218. if goodUnit.value == 1 {
  219. return fmt.Sprintf("%d%s", num, goodUnit.symbol)
  220. }
  221. res := math.Round(float64(num)/float64(goodUnit.value)*100) / 100
  222. return fmt.Sprintf("%.2f%s", res, goodUnit.symbol)
  223. }