├── .gitignore ├── mcache-ktop.conf ├── regexp.go ├── config.go ├── keystat.go ├── README.md ├── memcached.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.swp 3 | .DS_Store 4 | ._.DS_Store 5 | mcache-ktop 6 | -------------------------------------------------------------------------------- /mcache-ktop.conf: -------------------------------------------------------------------------------- 1 | { 2 | "regexps": [ 3 | {"re": "^foo_1.*", "name": "foo_1*"}, 4 | {"re": "^fo4_5.*", "name": "fo4_5*"} 5 | ], 6 | "interval": 3, 7 | "interface": "any", 8 | "ip": "", 9 | "port": 11211, 10 | "output_file": "", 11 | "sortby": "rcount" 12 | } 13 | -------------------------------------------------------------------------------- /regexp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "errors" 5 | "regexp" 6 | ) 7 | 8 | type RegexpKey struct { 9 | OriginalRegexp string 10 | CompiledRegexp *regexp.Regexp 11 | Name string 12 | } 13 | 14 | func NewRegexpKey(re string, name string) (regexp_key *RegexpKey, err error) { 15 | r := &RegexpKey{} 16 | compiled_regexp, err := regexp.Compile(re) 17 | if err != nil { 18 | return r, err 19 | } 20 | r.OriginalRegexp = re 21 | r.CompiledRegexp = compiled_regexp 22 | r.Name = name 23 | return r, nil 24 | } 25 | 26 | type RegexpKeys struct { 27 | regexp_keys []*RegexpKey 28 | } 29 | 30 | func NewRegexpKeys() *RegexpKeys { 31 | return &RegexpKeys{} 32 | } 33 | 34 | func (r *RegexpKeys) Add(regexp_key *RegexpKey) { 35 | r.regexp_keys = append(r.regexp_keys, regexp_key) 36 | } 37 | 38 | func (r *RegexpKeys) Match(key string) (string, error) { 39 | for _, re := range r.regexp_keys { 40 | if re.CompiledRegexp.Match([]byte(key)) { 41 | return re.Name, nil 42 | } 43 | } 44 | return key, nil 45 | } 46 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | type RegexpConfig struct { 9 | Name string `json:"name"` 10 | Re string `json:"re"` 11 | } 12 | 13 | type Config struct { 14 | Regexps []RegexpConfig `json:"regexps"` 15 | Interval int `json:"interval"` 16 | Interface string `json:"interface"` 17 | IpAddress string `json:"ip"` 18 | Port int `json:"port"` 19 | OutputFile string `json:"output_file"` 20 | SortBy string `json:"sortby"` 21 | } 22 | 23 | func NewConfig(config_data []byte) (config Config, err error) { 24 | config = Config{ 25 | Regexps: []RegexpConfig{}, 26 | Interval: 3, 27 | Interface: "any", 28 | IpAddress: "", 29 | Port: 11211, 30 | OutputFile: "", 31 | SortBy: "rcount", 32 | } 33 | if len(config_data) > 0 { 34 | err = json.Unmarshal(config_data, &config) 35 | if err != nil { 36 | return config, err 37 | } 38 | } 39 | for _, re := range config.Regexps { 40 | if re.Name == "" || re.Re == "" { 41 | return config, errors.New( 42 | "Config error: regular expressions must have both a 're' and 'name' field.") 43 | } 44 | } 45 | 46 | return config, nil 47 | } 48 | -------------------------------------------------------------------------------- /keystat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "container/heap" 5 | "sync" 6 | // "fmt" 7 | ) 8 | 9 | type Key struct { 10 | Name string 11 | Hits int 12 | } 13 | 14 | type KeyStat struct { 15 | RCount uint64 16 | WCount uint64 17 | RBytes uint64 18 | WBytes uint64 19 | Name string 20 | } 21 | 22 | type Stat struct { 23 | Lock sync.Mutex 24 | keys map[string]KeyStat 25 | } 26 | 27 | type OutputHeap []*KeyStat 28 | 29 | func (h OutputHeap) Len() int { return len(h) } 30 | 31 | func (h OutputHeap) Less(i, j int) bool { 32 | // Use global var to sort by 33 | switch Config_.SortBy { 34 | case "rbytes": return h[i].RBytes > h[j].RBytes 35 | case "wbytes": return h[i].WBytes > h[j].WBytes 36 | case "rcount": return h[i].RCount > h[j].RCount 37 | case "wcount": return h[i].WCount > h[j].WCount 38 | } 39 | return h[i].RCount > h[j].RCount 40 | } 41 | func (h OutputHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 42 | func (h *OutputHeap) Push(x interface{}) { 43 | *h = append(*h, x.(*KeyStat)) 44 | } 45 | 46 | func (h *OutputHeap) Pop() interface{} { 47 | old := *h 48 | n := len(old) 49 | x := old[n-1] 50 | *h = old[0 : n-1] 51 | return x 52 | } 53 | 54 | func NewStat() *Stat { 55 | s := &Stat{} 56 | s.keys = make(map[string]KeyStat) 57 | return s 58 | } 59 | 60 | func (s *Stat) Add(keys []KeyStat) { 61 | s.Lock.Lock() 62 | defer s.Lock.Unlock() 63 | 64 | for _, key := range keys { 65 | if k, ok := s.keys[key.Name]; ok { 66 | k.WBytes += key.WBytes 67 | k.RBytes += key.RBytes 68 | k.WCount += key.WCount 69 | k.RCount += key.RCount 70 | s.keys[key.Name] = k 71 | } else { 72 | s.keys[key.Name] = key 73 | } 74 | } 75 | } 76 | 77 | func (s *Stat) GetTopKeys() *OutputHeap { 78 | s.Lock.Lock() 79 | defer s.Lock.Unlock() 80 | 81 | top := &OutputHeap{} 82 | heap.Init(top) 83 | 84 | for _, key := range s.keys { 85 | heap.Push(top, &KeyStat{key.RCount, key.WCount, key.RBytes, key.WBytes, key.Name}) 86 | } 87 | return top 88 | } 89 | 90 | func (s *Stat) Rotate(clear bool) *Stat { 91 | s.Lock.Lock() 92 | defer s.Lock.Unlock() 93 | 94 | new_stat := s 95 | if clear { 96 | s.keys = make(map[string]KeyStat) 97 | } 98 | return new_stat 99 | } 100 | 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # mcache-ktop 4 | 5 | mcache-ktop is a libpcap based tool to interactively show the top of memcached keys used. 6 | 7 | - supports keys collapsing by regexp 8 | - sorting by most readable/writable keys 9 | 10 | Inspired by https://github.com/reddit/mcsauna and https://github.com/etsy/mctop 11 | 12 | ## Installation 13 | 14 | CentOS: 15 | ``` 16 | yum -y install libpcap-devel ncurses-devel 17 | git clone https://github.com/nesterwsx/mcache-ktop && cd mcache-ktop 18 | grep github main.go | xargs go get 19 | go build -o mcache-ktop *.go && ./mcache-ktop -i eth0 -d 1 20 | ``` 21 | 22 | Tested on CentOS x64 6/7 and MacOS 23 | 24 | ## Example 25 | ``` 26 | Reads Writes Read bytes Written bytes Key 27 | ------------------------------------------------------------------------------------------------------- 28 | 3,168,060 16,774 11 GB 140 MB user_* 29 | 1,718,936 75 1.4 GB 91 MB yi:SHOW_CREATE_TABLE_* 30 | 1,576,521 58 2.3 GB 89 MB yi:SHOW_FULL_COLUMNS_FROM* 31 | 1,185,086 7,755 2.9 GB 41 MB hrc* 32 | 631,177 0 8.9 GB 11 MB m_rus:* 33 | 454,220 31,501 180 MB 42 MB photo_id_* 34 | 444,676 1 27 MB 14 MB settings:cache_version 35 | 414,389 0 0 B 8.7 MB banlist: 36 | 279,263 11,324 21 MB 17 MB Vcl_* 37 | 217,824 0 1.6 GB 6.4 MB map_coords:* 38 | 195,357 2,275 18 MB 13 MB history_* 39 | 150,032 0 778 MB 3.6 MB ads_limit_* 40 | 97,768 0 144 MB 4.9 MB yi:default:`region`.`smart_blocks` 41 | 56,492 0 39 MB 2.5 MB yi:default:`log`.`announcement` 42 | 53,023 0 75 MB 1.4 MB sbdm_by_name:.ru 43 | 52,019 0 76 MB 2.3 MB meta_tags_rules:site_page_meta_tags 44 | ``` 45 | 46 | ## Command line options 47 | 48 | ``` 49 | Usage mcache-ktop [options]: 50 | 51 | -c string 52 | config file 53 | -d int 54 | update interval (seconds, default 3) 55 | -h string 56 | capture ip address (i.e. for bond with multiple IPs) 57 | -i string 58 | capture interface (default "any") 59 | -p int 60 | capture port (default 11211) 61 | -w string 62 | file to write output to 63 | ``` 64 | 65 | ### Example of config file 66 | ``` 67 | { 68 | "regexps": [ 69 | {"re": "^user_:.*", "name": "user_*"}, 70 | {"re": "^hrc:.*", "name": "hrc*"}, 71 | {"re": "^Vlc_.*", "name": "Vlc_*"}, 72 | {"re": "^history_.*", "name": "history_*"}, 73 | {"re": "^map_coords.*", "name": "map_coords*"} 74 | ], 75 | "interval": 3, 76 | "interface": "", 77 | "ip": "", 78 | "port": 11211, 79 | "quiet": false, 80 | "output_file": "" 81 | } 82 | ``` 83 | ## Known issues 84 | 85 | ### gopacket/pcap sometimes crash app 86 | . 87 | 88 | ### No binary protocol support 89 | There is currently no support for the binary protocol. However, if someone is using it and would like to submit a patch, it would be welcome. 90 | 91 | -------------------------------------------------------------------------------- /memcached.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "strconv" 6 | "strings" 7 | // "go/types" 8 | // "fmt" 9 | ) 10 | 11 | const ( 12 | ERR_NONE = iota 13 | ERR_NONE_SKIP 14 | ERR_NO_CMD 15 | ERR_INVALID_CMD 16 | ERR_INCOMPLETE_CMD 17 | ) 18 | 19 | // cmdKeyNoData processes a "get", "incr", "decr", "delete", or "touch" command, all 20 | // of which only allow for a single key to be passed and have no value field. 21 | // 22 | // On the wire, "get" looks like: 23 | // 24 | // get key\r\n 25 | // 26 | // And "incr", "decr", "touch" look like: 27 | // 28 | // cmd key value [noreply]\r\n 29 | // 30 | // Where "noreply" is an optional field that indicates whether the server 31 | // should return a response. 32 | func cmdKeyNoData(first_line string) (keys []KeyStat, cmd_err int) { 33 | 34 | var key KeyStat 35 | 36 | split_data := strings.Split(first_line, " ") 37 | if len(split_data) <= 1 { 38 | return []KeyStat{}, ERR_INCOMPLETE_CMD 39 | } 40 | 41 | key.Name = split_data[1] 42 | if key.Name == "" { 43 | return []KeyStat{}, ERR_INCOMPLETE_CMD 44 | } 45 | 46 | key.RCount = 1 47 | key.WBytes = uint64(len(first_line) + 2) 48 | 49 | return []KeyStat{key}, ERR_NONE 50 | } 51 | 52 | // cmdMultiKeyNoData processes a "gets" command, which allows for 53 | // multiple keys and has no value field. 54 | // 55 | // On the wire, "gets" looks like: 56 | // 57 | // gets key1 key2 key3\r\n 58 | func cmdMultiKeyNoData(first_line string) (keys []KeyStat, cmd_err int) { 59 | 60 | //var keys []KeyStat 61 | 62 | split_data := strings.Split(first_line, " ") 63 | if len(split_data) <= 1 { 64 | return []KeyStat{}, ERR_INCOMPLETE_CMD 65 | } 66 | 67 | for ndx, key_name := range split_data { 68 | if ndx == 0 { 69 | continue // Skip "cmd" 70 | } 71 | wb := uint64(len(first_line) + 2) 72 | keys = append(keys, KeyStat{Name:key_name, RCount:1, WBytes:wb}) 73 | } 74 | 75 | // Return parsed data 76 | return keys, ERR_NONE 77 | } 78 | 79 | // cmdKeyWithData processes a "set", "add", "replace", "append", "cas" or 80 | // "prepend" command, all of which only allow for a single key and have a 81 | // corresponding value field. 82 | // 83 | // These commands look like: 84 | // 85 | // cmd key flags exptime bytes [noreply]\r\n 86 | // \r\n 87 | // 88 | // Where "noreply" is an optional field that indicates whether the server 89 | // should return a response. 90 | func cmdKeyWithData(first_line string) (keys []KeyStat, cmd_err int) { 91 | 92 | var key KeyStat 93 | var bytes_str string 94 | var bytes_ int64 95 | var err error 96 | 97 | split_data := strings.Split(first_line, " ") 98 | if len(split_data) != 5 && len(split_data) != 6 { 99 | return []KeyStat{}, ERR_INCOMPLETE_CMD 100 | } 101 | 102 | key.Name, bytes_str = split_data[1], split_data[4] 103 | 104 | base := 10 105 | bitSize := 32 106 | bytes_, err = strconv.ParseInt(bytes_str, base, bitSize) 107 | if err != nil { 108 | return []KeyStat{}, ERR_INVALID_CMD 109 | } 110 | 111 | key.WCount = 1 112 | key.WBytes = uint64(bytes_) + uint64(len(first_line) + 4) 113 | 114 | return []KeyStat{key}, ERR_NONE 115 | 116 | } 117 | 118 | // cmdAnswerValue process responses to "get", "gets" commands 119 | // Answer looks like: 120 | // VALUE []\r\n 121 | // \r\n 122 | // END\r\n 123 | func cmdAnswerValue(first_line string) (keys []KeyStat, cmd_err int) { 124 | 125 | var bytes_ int64 126 | var err error 127 | var key KeyStat 128 | var bytes_str string 129 | 130 | split_data := strings.Split(first_line, " ") 131 | if len(split_data) != 4 && len(split_data) != 5 { 132 | return []KeyStat{}, ERR_INCOMPLETE_CMD 133 | } 134 | 135 | key.Name, bytes_str = split_data[1], split_data[3] 136 | 137 | base := 10 138 | bitSize := 32 139 | bytes_, err = strconv.ParseInt(bytes_str, base, bitSize) 140 | if err != nil { 141 | return []KeyStat{}, ERR_INVALID_CMD 142 | } 143 | 144 | key.RBytes = uint64(bytes_) + uint64(len(first_line) + 4) // What about 'END\r\n'? 145 | 146 | return []KeyStat{key}, ERR_NONE 147 | 148 | } 149 | 150 | // We don't track to which commands these answers belong 151 | // So we just ignore them 152 | func cmdAnswerIgnore (first_line string) (keys []KeyStat, cmd_err int) { 153 | return []KeyStat{}, ERR_NONE_SKIP 154 | } 155 | 156 | 157 | var CMD_PROCESSORS = map[string]func(first_line string) (keys []KeyStat, cmd_err int){ 158 | "get": cmdKeyNoData, 159 | "delete": cmdKeyNoData, 160 | "gets": cmdMultiKeyNoData, 161 | "set": cmdKeyWithData, 162 | "cas": cmdKeyWithData, 163 | "add": cmdKeyWithData, 164 | "replace": cmdKeyWithData, 165 | "append": cmdKeyWithData, 166 | "prepend": cmdKeyWithData, 167 | "incr": cmdKeyWithData, 168 | "decr": cmdKeyWithData, 169 | "value": cmdAnswerValue, 170 | "deleted": cmdAnswerIgnore, 171 | "stored": cmdAnswerIgnore, 172 | "not_stored": cmdAnswerIgnore, 173 | "exists": cmdAnswerIgnore, 174 | "not_found": cmdAnswerIgnore, 175 | "end": cmdAnswerIgnore, 176 | } 177 | 178 | 179 | func parse(app_data []byte) (keys []KeyStat, cmd_err int) { 180 | 181 | space_ndx := bytes.IndexByte(app_data, byte(' ')) 182 | if space_ndx == -1 { 183 | return []KeyStat{}, ERR_NONE_SKIP 184 | } 185 | 186 | newline_ndx := bytes.Index(app_data, []byte("\r\n")) 187 | if newline_ndx == -1 { 188 | return []KeyStat{}, ERR_NONE_SKIP 189 | } 190 | 191 | first_line := string(app_data[:newline_ndx]) 192 | split_data := strings.Split(first_line, " ") 193 | cmd := strings.ToLower(split_data[0]) 194 | if fn, ok := CMD_PROCESSORS[cmd]; ok { 195 | keys, cmd_err = fn(first_line) 196 | } else { 197 | return []KeyStat{}, ERR_INVALID_CMD 198 | } 199 | 200 | return keys, cmd_err 201 | } 202 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "container/heap" 5 | "flag" 6 | "fmt" 7 | "github.com/google/gopacket" 8 | "github.com/google/gopacket/pcap" 9 | "io/ioutil" 10 | "time" 11 | "github.com/rthornton128/goncurses" 12 | "github.com/dustin/go-humanize" 13 | "strings" 14 | "os" 15 | "os/signal" 16 | "syscall" 17 | "bufio" 18 | // "runtime/pprof" 19 | "strconv" 20 | "regexp" 21 | ) 22 | 23 | // TODO: Write tests 24 | // TODO: Use godep 25 | 26 | // Size of typical Jumbo frames 27 | // Hmm... What is the max size of 'mgets' may be? 28 | const CAPTURE_SIZE = 9000 29 | var Config_ Config 30 | const topLimit = 1000 31 | var startTs = time.Now() 32 | 33 | func formatCommas(num uint64) string { 34 | str := strconv.FormatUint(num, 10) 35 | re := regexp.MustCompile("(\\d+)(\\d{3})") 36 | for i := 0; i < (len(str) - 1) / 3; i++ { 37 | str = re.ReplaceAllString(str, "$1,$2") 38 | } 39 | return str 40 | } 41 | 42 | func showStat(config Config, stat *Stat) { 43 | sleep_duration := time.Duration(config.Interval) * time.Second 44 | time.Sleep(sleep_duration) 45 | 46 | // Do ncurses cleanup on SIGINT/SIGTERM and redraw screen on SIGWINCH 47 | sigs := make(chan os.Signal, 1) 48 | wchg := make(chan os.Signal, 1) 49 | wchgscr := false 50 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 51 | signal.Notify(wchg, syscall.SIGWINCH) 52 | 53 | stdscr, _ := goncurses.Init() 54 | defer goncurses.End() 55 | 56 | go func() { 57 | _ = <-sigs 58 | goncurses.End() 59 | // pprof.StopCPUProfile() 60 | os.Exit(0) 61 | }() 62 | 63 | go func() { 64 | for { 65 | _ = <-wchg 66 | wchgscr = true 67 | } 68 | }() 69 | 70 | var writer *bufio.Writer 71 | 72 | if config.OutputFile != "" { 73 | outfile, err := os.OpenFile(config.OutputFile, os.O_RDWR|os.O_CREATE, 0x644) 74 | if err != nil { 75 | panic(err) 76 | } 77 | defer outfile.Close() 78 | writer = bufio.NewWriter(outfile) 79 | } 80 | 81 | for { 82 | st := time.Now() 83 | if wchgscr { 84 | goncurses.End() 85 | stdscr, _ = goncurses.Init() 86 | stdscr.Erase() 87 | wchgscr = false 88 | } 89 | 90 | rows, cols := stdscr.MaxYX() 91 | 92 | clear := false 93 | if config.OutputFile != "" { 94 | clear = true 95 | } 96 | rotated_stat := stat.Rotate(clear) 97 | top := rotated_stat.GetTopKeys() 98 | 99 | output := "" 100 | output = " Reads Writes Read bytes Written bytes Key\n" 101 | output += strings.Repeat("-", cols) 102 | 103 | foutput := "" 104 | i := 0 105 | var totalRC, totalWC, totalRB, totalWB uint64 106 | for { 107 | if top.Len() == 0 || i > topLimit { 108 | break 109 | } 110 | 111 | key := heap.Pop(top) 112 | 113 | if i <= (rows - 6) { 114 | output += fmt.Sprintf("%12s %12s %19s %19s %s\n", 115 | formatCommas(key.(*KeyStat).RCount), 116 | formatCommas(key.(*KeyStat).WCount), 117 | humanize.Bytes(key.(*KeyStat).RBytes), 118 | humanize.Bytes(key.(*KeyStat).WBytes), 119 | key.(*KeyStat).Name) 120 | } 121 | 122 | totalRC += key.(*KeyStat).RCount 123 | totalWC += key.(*KeyStat).WCount 124 | totalRB += key.(*KeyStat).RBytes 125 | totalWB += key.(*KeyStat).WBytes 126 | 127 | if config.OutputFile != "" { 128 | foutput += fmt.Sprintf("%12d %12d %19d %19d %s\n", 129 | key.(*KeyStat).RCount, 130 | key.(*KeyStat).WCount, 131 | key.(*KeyStat).RBytes, 132 | key.(*KeyStat).WBytes, 133 | key.(*KeyStat).Name) 134 | } 135 | i += 1 136 | } 137 | 138 | tsDiff := time.Now().Sub(startTs) 139 | output += strings.Repeat("-", cols) 140 | output += fmt.Sprintf("%12s %12s %19s %19s", 141 | formatCommas(totalRC/uint64(tsDiff.Seconds())) + "/sec", 142 | formatCommas(totalWC/uint64(tsDiff.Seconds())) + "/sec", 143 | humanize.Bytes(totalRB/uint64(tsDiff.Seconds())) + "/sec", 144 | humanize.Bytes(totalWB/uint64(tsDiff.Seconds())) + "/sec") 145 | 146 | stdscr.MovePrint(0, 0, output) 147 | stdscr.Refresh() 148 | goncurses.Cursor(0) 149 | 150 | if config.OutputFile != "" { 151 | sz, err := writer.WriteString(foutput) 152 | if err != nil { 153 | fmt.Println(sz, err) 154 | } 155 | writer.Flush() 156 | } 157 | 158 | elapsed := time.Now().Sub(st) 159 | time.Sleep(sleep_duration - elapsed) 160 | } 161 | } 162 | 163 | func main() { 164 | 165 | var err error 166 | /* f, err := os.Create("mcache-ktop.pprof") 167 | if err != nil { 168 | panic(err) 169 | } 170 | pprof.StartCPUProfile(f) 171 | defer pprof.StopCPUProfile() 172 | */ 173 | interval := flag.Int("d", 1, "update interval (seconds, default 1)") 174 | net_interface := flag.String("i", "any", "capture interface (default any)") 175 | ip := flag.String("h", "", "capture ip address (i.e. for bond with multiple IPs)") 176 | port := flag.Int("p", 11211, "capture port") 177 | // output_file := flag.String("o", "", "file to write output to") 178 | config_file := flag.String("c", "/etc/mcache-ktop.conf", "config file") 179 | sortby := flag.String("s", "rcount", "sort by (rcount|wcount|rbytes|wbytes)") 180 | 181 | flag.Parse() 182 | 183 | if *config_file != "" { 184 | config_data, _ := ioutil.ReadFile(*config_file) 185 | Config_, err = NewConfig(config_data) 186 | } else { 187 | Config_, err = NewConfig([]byte{}) 188 | } 189 | if err != nil { 190 | panic(err) 191 | } 192 | 193 | if *interval != 0 { 194 | Config_.Interval = *interval 195 | } 196 | if *net_interface != "" { 197 | Config_.Interface = *net_interface 198 | } 199 | if *ip != "" { 200 | Config_.IpAddress = *ip 201 | } 202 | if *port != 0 { 203 | Config_.Port = *port 204 | } 205 | /* if *output_file != "" { 206 | Config_.OutputFile = *output_file 207 | }*/ 208 | if *sortby != "" { 209 | Config_.SortBy = *sortby 210 | } 211 | re_keys := NewRegexpKeys() 212 | for _, re := range Config_.Regexps { 213 | regexp_key, err := NewRegexpKey(re.Re, re.Name) 214 | if err != nil { 215 | panic(err) 216 | } 217 | re_keys.Add(regexp_key) 218 | } 219 | 220 | kstat := NewStat() 221 | 222 | handle, err := pcap.OpenLive(Config_.Interface, CAPTURE_SIZE, true, pcap.BlockForever) 223 | if err != nil { 224 | panic(err) 225 | } 226 | var filter string 227 | if *ip != "" { 228 | filter = fmt.Sprintf("tcp and host %s && port %d", Config_.IpAddress, Config_.Port) 229 | } else { 230 | filter = fmt.Sprintf("tcp and port %d", Config_.Port) 231 | } 232 | 233 | err = handle.SetBPFFilter(filter) 234 | if err != nil { 235 | panic(err) 236 | } 237 | packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) 238 | 239 | go showStat(Config_, kstat) 240 | 241 | var ( 242 | payload []byte 243 | keys []KeyStat 244 | cmd_err int 245 | ) 246 | for packet := range packetSource.Packets() { 247 | app_data := packet.ApplicationLayer() 248 | if app_data == nil { 249 | continue 250 | } 251 | payload = app_data.Payload() 252 | // payload2 := payload 253 | // fmt.Printf("%d, %s\n", cmd_err, payload2) 254 | 255 | if len(payload) < 1 { 256 | continue // nothing to parse 257 | } 258 | 259 | keys, cmd_err = parse(payload) 260 | 261 | if cmd_err == ERR_NO_CMD { 262 | continue 263 | } 264 | 265 | if cmd_err == ERR_NONE { 266 | 267 | if len(Config_.Regexps) == 0 { 268 | kstat.Add(keys) 269 | } else { 270 | matches := []KeyStat{} 271 | for _, key := range keys { 272 | key.Name, err = re_keys.Match(key.Name) 273 | matches = append(matches, key) 274 | } 275 | kstat.Add(matches) 276 | } 277 | } 278 | /* if cmd_err != ERR_NONE_SKIP && cmd_err != ERR_NONE { 279 | fmt.Printf("%d, %s\n", cmd_err, payload2) 280 | }*/ 281 | } 282 | } 283 | --------------------------------------------------------------------------------