metrics.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "os"
  7. "sort"
  8. "strconv"
  9. "strings"
  10. "time"
  11. log "github.com/sirupsen/logrus"
  12. "gopkg.in/yaml.v2"
  13. "github.com/olekukonko/tablewriter"
  14. dto "github.com/prometheus/client_model/go"
  15. "github.com/prometheus/prom2json"
  16. "github.com/spf13/cobra"
  17. )
  18. /*This is a complete rip from prom2json*/
  19. func ShowPrometheus(url string) {
  20. mfChan := make(chan *dto.MetricFamily, 1024)
  21. // Start with the DefaultTransport for sane defaults.
  22. transport := http.DefaultTransport.(*http.Transport).Clone()
  23. // Conservatively disable HTTP keep-alives as this program will only
  24. // ever need a single HTTP request.
  25. transport.DisableKeepAlives = true
  26. // Timeout early if the server doesn't even return the headers.
  27. transport.ResponseHeaderTimeout = time.Minute
  28. go func() {
  29. err := prom2json.FetchMetricFamilies(url, mfChan, transport)
  30. if err != nil {
  31. log.Fatalf("failed to fetch prometheus metrics : %v", err)
  32. }
  33. }()
  34. result := []*prom2json.Family{}
  35. for mf := range mfChan {
  36. result = append(result, prom2json.NewFamily(mf))
  37. }
  38. log.Debugf("Finished reading prometheus output, %d entries", len(result))
  39. /*walk*/
  40. acquis_stats := map[string]map[string]int{}
  41. parsers_stats := map[string]map[string]int{}
  42. buckets_stats := map[string]map[string]int{}
  43. for idx, fam := range result {
  44. if !strings.HasPrefix(fam.Name, "cs_") {
  45. continue
  46. }
  47. log.Debugf("round %d", idx)
  48. for _, m := range fam.Metrics {
  49. metric := m.(prom2json.Metric)
  50. name, ok := metric.Labels["name"]
  51. if !ok {
  52. log.Debugf("no name in Metric")
  53. }
  54. source, ok := metric.Labels["source"]
  55. if !ok {
  56. log.Debugf("no source in Metric")
  57. }
  58. value := m.(prom2json.Metric).Value
  59. fval, err := strconv.ParseFloat(value, 32)
  60. if err != nil {
  61. log.Errorf("Unexpected int value %s : %s", value, err)
  62. }
  63. ival := int(fval)
  64. switch fam.Name {
  65. /*buckets*/
  66. case "cs_bucket_create":
  67. if _, ok := buckets_stats[name]; !ok {
  68. buckets_stats[name] = make(map[string]int)
  69. }
  70. buckets_stats[name]["instanciation"] += ival
  71. case "cs_bucket_count":
  72. if _, ok := buckets_stats[name]; !ok {
  73. buckets_stats[name] = make(map[string]int)
  74. }
  75. buckets_stats[name]["curr_count"] += ival
  76. case "cs_bucket_overflow":
  77. if _, ok := buckets_stats[name]; !ok {
  78. buckets_stats[name] = make(map[string]int)
  79. }
  80. buckets_stats[name]["overflow"] += ival
  81. case "cs_bucket_pour":
  82. if _, ok := buckets_stats[name]; !ok {
  83. buckets_stats[name] = make(map[string]int)
  84. }
  85. if _, ok := acquis_stats[source]; !ok {
  86. acquis_stats[source] = make(map[string]int)
  87. }
  88. buckets_stats[name]["pour"] += ival
  89. acquis_stats[source]["pour"] += ival
  90. case "cs_bucket_underflow":
  91. if _, ok := buckets_stats[name]; !ok {
  92. buckets_stats[name] = make(map[string]int)
  93. }
  94. buckets_stats[name]["underflow"] += ival
  95. /*acquis*/
  96. case "cs_reader_hits":
  97. if _, ok := acquis_stats[source]; !ok {
  98. acquis_stats[source] = make(map[string]int)
  99. }
  100. acquis_stats[source]["reads"] += ival
  101. case "cs_parser_hits_ok":
  102. if _, ok := acquis_stats[source]; !ok {
  103. acquis_stats[source] = make(map[string]int)
  104. }
  105. acquis_stats[source]["parsed"] += ival
  106. case "cs_parser_hits_ko":
  107. if _, ok := acquis_stats[source]; !ok {
  108. acquis_stats[source] = make(map[string]int)
  109. }
  110. acquis_stats[source]["unparsed"] += ival
  111. case "cs_node_hits":
  112. if _, ok := parsers_stats[name]; !ok {
  113. parsers_stats[name] = make(map[string]int)
  114. }
  115. parsers_stats[name]["hits"] += ival
  116. case "cs_node_hits_ok":
  117. if _, ok := parsers_stats[name]; !ok {
  118. parsers_stats[name] = make(map[string]int)
  119. }
  120. parsers_stats[name]["parsed"] += ival
  121. default:
  122. continue
  123. }
  124. }
  125. }
  126. if config.output == "human" {
  127. atable := tablewriter.NewWriter(os.Stdout)
  128. atable.SetHeader([]string{"Source", "Lines read", "Lines parsed", "Lines unparsed", "Lines poured to bucket"})
  129. var sortedKeys []string
  130. //sort to keep consistent order when printing
  131. sortedKeys = []string{}
  132. for akey := range acquis_stats {
  133. sortedKeys = append(sortedKeys, akey)
  134. }
  135. sort.Strings(sortedKeys)
  136. for _, alabel := range sortedKeys {
  137. if alabel == "" {
  138. continue
  139. }
  140. astats := acquis_stats[alabel]
  141. row := []string{}
  142. row = append(row, alabel) //name
  143. for _, sl := range []string{"reads", "parsed", "unparsed", "pour"} {
  144. if v, ok := astats[sl]; ok {
  145. row = append(row, fmt.Sprintf("%d", v))
  146. } else {
  147. row = append(row, "-")
  148. }
  149. }
  150. atable.Append(row)
  151. }
  152. btable := tablewriter.NewWriter(os.Stdout)
  153. btable.SetHeader([]string{"Bucket", "Current Count", "Overflows", "Instanciated", "Poured", "Expired"})
  154. //sort to keep consistent order when printing
  155. sortedKeys = []string{}
  156. for akey := range buckets_stats {
  157. sortedKeys = append(sortedKeys, akey)
  158. }
  159. sort.Strings(sortedKeys)
  160. for _, blabel := range sortedKeys {
  161. if blabel == "" {
  162. continue
  163. }
  164. bstats := buckets_stats[blabel]
  165. row := []string{}
  166. row = append(row, blabel) //name
  167. for _, sl := range []string{"overflow", "curr_count", "instanciation", "pour", "underflow"} {
  168. if v, ok := bstats[sl]; ok {
  169. row = append(row, fmt.Sprintf("%d", v))
  170. } else {
  171. row = append(row, "-")
  172. }
  173. }
  174. btable.Append(row)
  175. }
  176. ptable := tablewriter.NewWriter(os.Stdout)
  177. ptable.SetHeader([]string{"Parsers", "Hits", "Parsed", "Unparsed"})
  178. //sort to keep consistent order when printing
  179. sortedKeys = []string{}
  180. for akey := range parsers_stats {
  181. sortedKeys = append(sortedKeys, akey)
  182. }
  183. sort.Strings(sortedKeys)
  184. for _, plabel := range sortedKeys {
  185. if plabel == "" {
  186. continue
  187. }
  188. pstats := parsers_stats[plabel]
  189. row := []string{}
  190. row = append(row, plabel) //name
  191. hits := 0
  192. parsed := 0
  193. for _, sl := range []string{"hits", "parsed"} {
  194. if v, ok := pstats[sl]; ok {
  195. row = append(row, fmt.Sprintf("%d", v))
  196. if sl == "hits" {
  197. hits = v
  198. } else if sl == "parsed" {
  199. parsed = v
  200. }
  201. } else {
  202. row = append(row, "-")
  203. }
  204. }
  205. row = append(row, fmt.Sprintf("%d", hits-parsed))
  206. ptable.Append(row)
  207. }
  208. log.Printf("Buckets Metrics:")
  209. btable.Render() // Send output
  210. log.Printf("Acquisition Metrics:")
  211. atable.Render() // Send output
  212. log.Printf("Parser Metrics:")
  213. ptable.Render() // Send output
  214. } else if config.output == "json" {
  215. for _, val := range []map[string]map[string]int{acquis_stats, parsers_stats, buckets_stats} {
  216. x, err := json.MarshalIndent(val, "", " ")
  217. if err != nil {
  218. log.Fatalf("failed to unmarshal metrics : %v", err)
  219. }
  220. fmt.Printf("%s\n", string(x))
  221. }
  222. } else if config.output == "raw" {
  223. for _, val := range []map[string]map[string]int{acquis_stats, parsers_stats, buckets_stats} {
  224. x, err := yaml.Marshal(val)
  225. if err != nil {
  226. log.Fatalf("failed to unmarshal metrics : %v", err)
  227. }
  228. fmt.Printf("%s\n", string(x))
  229. }
  230. }
  231. }
  232. var purl string
  233. func NewMetricsCmd() *cobra.Command {
  234. /* ---- UPDATE COMMAND */
  235. var cmdMetrics = &cobra.Command{
  236. Use: "metrics",
  237. Short: "Display crowdsec prometheus metrics.",
  238. Long: `Fetch metrics from the prometheus server and display them in a human-friendly way`,
  239. Args: cobra.ExactArgs(0),
  240. Run: func(cmd *cobra.Command, args []string) {
  241. ShowPrometheus(purl)
  242. },
  243. }
  244. cmdMetrics.PersistentFlags().StringVarP(&purl, "url", "u", "http://127.0.0.1:6060/metrics", "Prometheus url")
  245. return cmdMetrics
  246. }