123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605 |
- package main
- import (
- "fmt"
- "io"
- "sort"
- "strconv"
- "github.com/aquasecurity/table"
- log "github.com/sirupsen/logrus"
- "github.com/crowdsecurity/go-cs-lib/maptools"
- )
- // ErrNilTable means a nil pointer was passed instead of a table instance. This is a programming error.
- var ErrNilTable = fmt.Errorf("nil table")
- func lapiMetricsToTable(t *table.Table, stats map[string]map[string]map[string]int) int {
- // stats: machine -> route -> method -> count
- // sort keys to keep consistent order when printing
- machineKeys := []string{}
- for k := range stats {
- machineKeys = append(machineKeys, k)
- }
- sort.Strings(machineKeys)
- numRows := 0
- for _, machine := range machineKeys {
- // oneRow: route -> method -> count
- machineRow := stats[machine]
- for routeName, route := range machineRow {
- for methodName, count := range route {
- row := []string{
- machine,
- routeName,
- methodName,
- }
- if count != 0 {
- row = append(row, strconv.Itoa(count))
- } else {
- row = append(row, "-")
- }
- t.AddRow(row...)
- numRows++
- }
- }
- }
- return numRows
- }
- func wlMetricsToTable(t *table.Table, stats map[string]map[string]map[string]int, noUnit bool) (int, error) {
- if t == nil {
- return 0, ErrNilTable
- }
- numRows := 0
- for _, name := range maptools.SortedKeys(stats) {
- for _, reason := range maptools.SortedKeys(stats[name]) {
- row := []string{
- name,
- reason,
- "-",
- "-",
- }
- for _, action := range maptools.SortedKeys(stats[name][reason]) {
- value := stats[name][reason][action]
- switch action {
- case "whitelisted":
- row[3] = strconv.Itoa(value)
- case "hits":
- row[2] = strconv.Itoa(value)
- default:
- log.Debugf("unexpected counter '%s' for whitelists = %d", action, value)
- }
- }
- t.AddRow(row...)
- numRows++
- }
- }
- return numRows, nil
- }
- func metricsToTable(t *table.Table, stats map[string]map[string]int, keys []string, noUnit bool) (int, error) {
- if t == nil {
- return 0, ErrNilTable
- }
- numRows := 0
- for _, alabel := range maptools.SortedKeys(stats) {
- astats, ok := stats[alabel]
- if !ok {
- continue
- }
- row := []string{
- alabel,
- }
- for _, sl := range keys {
- if v, ok := astats[sl]; ok && v != 0 {
- numberToShow := strconv.Itoa(v)
- if !noUnit {
- numberToShow = formatNumber(v)
- }
- row = append(row, numberToShow)
- } else {
- row = append(row, "-")
- }
- }
- t.AddRow(row...)
- numRows++
- }
- return numRows, nil
- }
- func (s statBucket) Description() (string, string) {
- return "Bucket Metrics",
- `Measure events in different scenarios. Current count is the number of buckets during metrics collection. ` +
- `Overflows are past event-producing buckets, while Expired are the ones that didn’t receive enough events to Overflow.`
- }
- func (s statBucket) Process(bucket, metric string, val int) {
- if _, ok := s[bucket]; !ok {
- s[bucket] = make(map[string]int)
- }
- s[bucket][metric] += val
- }
- func (s statBucket) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Bucket", "Current Count", "Overflows", "Instantiated", "Poured", "Expired")
- t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
- keys := []string{"curr_count", "overflow", "instantiation", "pour", "underflow"}
- if numRows, err := metricsToTable(t, s, keys, noUnit); err != nil {
- log.Warningf("while collecting bucket stats: %s", err)
- } else if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statAcquis) Description() (string, string) {
- return "Acquisition Metrics",
- `Measures the lines read, parsed, and unparsed per datasource. ` +
- `Zero read lines indicate a misconfigured or inactive datasource. ` +
- `Zero parsed lines mean the parser(s) failed. ` +
- `Non-zero parsed lines are fine as crowdsec selects relevant lines.`
- }
- func (s statAcquis) Process(source, metric string, val int) {
- if _, ok := s[source]; !ok {
- s[source] = make(map[string]int)
- }
- s[source][metric] += val
- }
- func (s statAcquis) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Source", "Lines read", "Lines parsed", "Lines unparsed", "Lines poured to bucket", "Lines whitelisted")
- t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
- keys := []string{"reads", "parsed", "unparsed", "pour", "whitelisted"}
- if numRows, err := metricsToTable(t, s, keys, noUnit); err != nil {
- log.Warningf("while collecting acquis stats: %s", err)
- } else if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statAppsecEngine) Description() (string, string) {
- return "Appsec Metrics",
- `Measures the number of parsed and blocked requests by the AppSec Component.`
- }
- func (s statAppsecEngine) Process(appsecEngine, metric string, val int) {
- if _, ok := s[appsecEngine]; !ok {
- s[appsecEngine] = make(map[string]int)
- }
- s[appsecEngine][metric] += val
- }
- func (s statAppsecEngine) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Appsec Engine", "Processed", "Blocked")
- t.SetAlignment(table.AlignLeft, table.AlignLeft)
- keys := []string{"processed", "blocked"}
- if numRows, err := metricsToTable(t, s, keys, noUnit); err != nil {
- log.Warningf("while collecting appsec stats: %s", err)
- } else if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statAppsecRule) Description() (string, string) {
- return "Appsec Rule Metrics",
- `Provides “per AppSec Component” information about the number of matches for loaded AppSec Rules.`
- }
- func (s statAppsecRule) Process(appsecEngine, appsecRule string, metric string, val int) {
- if _, ok := s[appsecEngine]; !ok {
- s[appsecEngine] = make(map[string]map[string]int)
- }
- if _, ok := s[appsecEngine][appsecRule]; !ok {
- s[appsecEngine][appsecRule] = make(map[string]int)
- }
- s[appsecEngine][appsecRule][metric] += val
- }
- func (s statAppsecRule) Table(out io.Writer, noUnit bool, showEmpty bool) {
- for appsecEngine, appsecEngineRulesStats := range s {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Rule ID", "Triggered")
- t.SetAlignment(table.AlignLeft, table.AlignLeft)
- keys := []string{"triggered"}
- if numRows, err := metricsToTable(t, appsecEngineRulesStats, keys, noUnit); err != nil {
- log.Warningf("while collecting appsec rules stats: %s", err)
- } else if numRows > 0 || showEmpty {
- renderTableTitle(out, fmt.Sprintf("\nAppsec '%s' Rules Metrics:", appsecEngine))
- t.Render()
- }
- }
- }
- func (s statWhitelist) Description() (string, string) {
- return "Whitelist Metrics",
- `Tracks the number of events processed and possibly whitelisted by each parser whitelist.`
- }
- func (s statWhitelist) Process(whitelist, reason, metric string, val int) {
- if _, ok := s[whitelist]; !ok {
- s[whitelist] = make(map[string]map[string]int)
- }
- if _, ok := s[whitelist][reason]; !ok {
- s[whitelist][reason] = make(map[string]int)
- }
- s[whitelist][reason][metric] += val
- }
- func (s statWhitelist) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Whitelist", "Reason", "Hits", "Whitelisted")
- t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
- if numRows, err := wlMetricsToTable(t, s, noUnit); err != nil {
- log.Warningf("while collecting parsers stats: %s", err)
- } else if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statParser) Description() (string, string) {
- return "Parser Metrics",
- `Tracks the number of events processed by each parser and indicates success of failure. ` +
- `Zero parsed lines means the parer(s) failed. ` +
- `Non-zero unparsed lines are fine as crowdsec select relevant lines.`
- }
- func (s statParser) Process(parser, metric string, val int) {
- if _, ok := s[parser]; !ok {
- s[parser] = make(map[string]int)
- }
- s[parser][metric] += val
- }
- func (s statParser) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Parsers", "Hits", "Parsed", "Unparsed")
- t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
- keys := []string{"hits", "parsed", "unparsed"}
- if numRows, err := metricsToTable(t, s, keys, noUnit); err != nil {
- log.Warningf("while collecting parsers stats: %s", err)
- } else if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statStash) Description() (string, string) {
- return "Parser Stash Metrics",
- `Tracks the status of stashes that might be created by various parsers and scenarios.`
- }
- func (s statStash) Process(name, mtype string, val int) {
- s[name] = struct {
- Type string
- Count int
- }{
- Type: mtype,
- Count: val,
- }
- }
- func (s statStash) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Name", "Type", "Items")
- t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
- // unfortunately, we can't reuse metricsToTable as the structure is too different :/
- numRows := 0
- for _, alabel := range maptools.SortedKeys(s) {
- astats := s[alabel]
- row := []string{
- alabel,
- astats.Type,
- strconv.Itoa(astats.Count),
- }
- t.AddRow(row...)
- numRows++
- }
- if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statLapi) Description() (string, string) {
- return "Local API Metrics",
- `Monitors the requests made to local API routes.`
- }
- func (s statLapi) Process(route, method string, val int) {
- if _, ok := s[route]; !ok {
- s[route] = make(map[string]int)
- }
- s[route][method] += val
- }
- func (s statLapi) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Route", "Method", "Hits")
- t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
- // unfortunately, we can't reuse metricsToTable as the structure is too different :/
- numRows := 0
- for _, alabel := range maptools.SortedKeys(s) {
- astats := s[alabel]
- subKeys := []string{}
- for skey := range astats {
- subKeys = append(subKeys, skey)
- }
- sort.Strings(subKeys)
- for _, sl := range subKeys {
- row := []string{
- alabel,
- sl,
- strconv.Itoa(astats[sl]),
- }
- t.AddRow(row...)
- numRows++
- }
- }
- if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statLapiMachine) Description() (string, string) {
- return "Local API Machines Metrics",
- `Tracks the number of calls to the local API from each registered machine.`
- }
- func (s statLapiMachine) Process(machine, route, method string, val int) {
- if _, ok := s[machine]; !ok {
- s[machine] = make(map[string]map[string]int)
- }
- if _, ok := s[machine][route]; !ok {
- s[machine][route] = make(map[string]int)
- }
- s[machine][route][method] += val
- }
- func (s statLapiMachine) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Machine", "Route", "Method", "Hits")
- t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
- numRows := lapiMetricsToTable(t, s)
- if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statLapiBouncer) Description() (string, string) {
- return "Local API Bouncers Metrics",
- `Tracks total hits to remediation component related API routes.`
- }
- func (s statLapiBouncer) Process(bouncer, route, method string, val int) {
- if _, ok := s[bouncer]; !ok {
- s[bouncer] = make(map[string]map[string]int)
- }
- if _, ok := s[bouncer][route]; !ok {
- s[bouncer][route] = make(map[string]int)
- }
- s[bouncer][route][method] += val
- }
- func (s statLapiBouncer) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Bouncer", "Route", "Method", "Hits")
- t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
- numRows := lapiMetricsToTable(t, s)
- if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statLapiDecision) Description() (string, string) {
- return "Local API Bouncers Decisions",
- `Tracks the number of empty/non-empty answers from LAPI to bouncers that are working in "live" mode.`
- }
- func (s statLapiDecision) Process(bouncer, fam string, val int) {
- if _, ok := s[bouncer]; !ok {
- s[bouncer] = struct {
- NonEmpty int
- Empty int
- }{}
- }
- x := s[bouncer]
- switch fam {
- case "cs_lapi_decisions_ko_total":
- x.Empty += val
- case "cs_lapi_decisions_ok_total":
- x.NonEmpty += val
- }
- s[bouncer] = x
- }
- func (s statLapiDecision) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Bouncer", "Empty answers", "Non-empty answers")
- t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
- numRows := 0
- for bouncer, hits := range s {
- t.AddRow(
- bouncer,
- strconv.Itoa(hits.Empty),
- strconv.Itoa(hits.NonEmpty),
- )
- numRows++
- }
- if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statDecision) Description() (string, string) {
- return "Local API Decisions",
- `Provides information about all currently active decisions. ` +
- `Includes both local (crowdsec) and global decisions (CAPI), and lists subscriptions (lists).`
- }
- func (s statDecision) Process(reason, origin, action string, val int) {
- if _, ok := s[reason]; !ok {
- s[reason] = make(map[string]map[string]int)
- }
- if _, ok := s[reason][origin]; !ok {
- s[reason][origin] = make(map[string]int)
- }
- s[reason][origin][action] += val
- }
- func (s statDecision) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Reason", "Origin", "Action", "Count")
- t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
- numRows := 0
- for reason, origins := range s {
- for origin, actions := range origins {
- for action, hits := range actions {
- t.AddRow(
- reason,
- origin,
- action,
- strconv.Itoa(hits),
- )
- numRows++
- }
- }
- }
- if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
- func (s statAlert) Description() (string, string) {
- return "Local API Alerts",
- `Tracks the total number of past and present alerts for the installed scenarios.`
- }
- func (s statAlert) Process(reason string, val int) {
- s[reason] += val
- }
- func (s statAlert) Table(out io.Writer, noUnit bool, showEmpty bool) {
- t := newTable(out)
- t.SetRowLines(false)
- t.SetHeaders("Reason", "Count")
- t.SetAlignment(table.AlignLeft, table.AlignLeft)
- numRows := 0
- for scenario, hits := range s {
- t.AddRow(
- scenario,
- strconv.Itoa(hits),
- )
- numRows++
- }
- if numRows > 0 || showEmpty {
- title, _ := s.Description()
- renderTableTitle(out, "\n"+title+":")
- t.Render()
- }
- }
|