redis_scanner 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a0480eb1d36893c2716f1e22908890f0c8f9b21
4
- data.tar.gz: e700af17696a802085fcc0ef0f5abe775d7f4f11
3
+ metadata.gz: f4d4379646a6674439ca7ad275828c5881c97901
4
+ data.tar.gz: 36e42b13cab65c468e71dded6a2edfafeaba3671
5
5
  SHA512:
6
- metadata.gz: 17992d3625d7aa84c4c6f632249d1d9fc0a669531112d9b43b386b3093908ce7febb5430ee438ac34fd5c15950408340ef2547578c8689856fcfd1f3302c5e4d
7
- data.tar.gz: 6bca4cbc90c4716e0858f24cc6dacd60887f45142430bbd6343e60e72f6d599146b053bed86d60cb375c8d008343bdfd8d546f3c86d0cc1ecaf9ebd1117776ea
6
+ metadata.gz: 1d4180f672190a047a5a39269b1d726ad8b26c3dfe9d640d8839006c0be2050d96a185bbbaf16c883f62cc05afa6387f00ba62de2c8c325aab542431c8cbc21e
7
+ data.tar.gz: 83599028ff33ec24a5f573a5332aedb1db25a03c9a7e3daebd364210cffc34e52e6d39af65052a7ef0aaecbc5cf90ff0df278b8d5ad27b1acc5d5fb4a0193fd6
data/CHANGELOG.md CHANGED
@@ -1,12 +1,19 @@
1
+ ## v0.1.3
2
+
3
+ * Provide key's detail information which includes type and size.
4
+ * Provide table format output. And you can use simpe output also.
5
+ * Add progress bar.
6
+ * Support limit, format and detail options.
7
+ * Refactor codes.Add more auto test cases.
1
8
 
2
9
  ## v0.1.2
3
10
 
4
- * Support date pattern
5
- * Add auto test cases
11
+ * Support date pattern.
12
+ * Add auto test cases.
6
13
 
7
14
  ## v0.1.1
8
15
 
9
- * Add redis_scanner to executable file
16
+ * Add redis_scanner to executable file.
10
17
 
11
18
  ## v0.1.0
12
19
 
data/README.md CHANGED
@@ -4,6 +4,8 @@ RedisScanner is a tiny tool for scanning all redis keys and creating statistic r
4
4
 
5
5
  * Scans keys using *scan* replacing *keys* command.
6
6
  * Creates statistic result by key's pattern.
7
+ * Provide key's detail information which includes type and size.
8
+ * Provide table format output..
7
9
 
8
10
  ## Installation
9
11
 
@@ -30,19 +32,40 @@ redis_scanner
30
32
  The Output is like this:
31
33
 
32
34
  ```shell
33
- =========result is=========
34
- demo:user:<id>:counter 10000
35
- u:<uuid>:pf 52
36
- sidekiq_demo:stat:failed:<date> 4
37
- sidekiq_demo:stat:processed:<date> 4
38
- _sp_one:queue:default 1
39
- bh:queues 1
40
- bh:retry 1
41
- bh:stat:failed 1
42
- bh:stat:processed 1
43
- bus_app:app_two 1
35
+ +------------------------------------+-------+
36
+ | Key | Count |
37
+ +------------------------------------+-------+
38
+ | demo:user:<id>:counter | 10000 |
39
+ | u:<uuid>:pf | 52 |
40
+ | sidekiq_demo:stat:failed:<date> | 4 |
41
+ | sidekiq_demo:stat:processed:<date> | 4 |
42
+ | _sp_one:queue:default | 1 |
43
+ | bh:queues | 1 |
44
44
  ...
45
- ===========================
45
+ +------------------------------------+-------+
46
+ ```
47
+
48
+ ```shell
49
+ redis_scanner -d
50
+ ```
51
+
52
+ The Output is like this:
53
+
54
+ ```shell
55
+ +------------------------------------+-------+--------+---------+
56
+ | Key | Count | Size | AvgSize |
57
+ +------------------------------------+-------+--------+---------+
58
+ | demo:user:<id>:counter | 10000 | | |
59
+ | > string | 10000 | 927510 | 92.75 |
60
+ | u:<uuid>:pf | 52 | | |
61
+ | > hash | 52 | 108 | 2.08 |
62
+ | sidekiq_demo:stat:failed:<date> | 4 | | |
63
+ | > string | 4 | 5 | 1.25 |
64
+ | sidekiq_demo:stat:processed:<date> | 4 | | |
65
+ | > string | 4 | 6 | 1.5 |
66
+ | _sp_one:queue:default | 1 | | |
67
+ | > list | 1 | 1 | 1.0 |
68
+ +------------------------------------+-------+--------+---------+
46
69
  ```
47
70
 
48
71
  * Scan keys with some pattern
data/bin/redis_scanner CHANGED
@@ -9,14 +9,28 @@ options = {}
9
9
  OptionParser.new do |opts|
10
10
  opts.banner = "Usage: redis_scanner [options]"
11
11
 
12
- opts.on("-f FILE", "--file FILE", "Output file") do |v|
12
+ opts.on("-f FILE", "--file FILE", "Output to file") do |v|
13
13
  options[:file] = v
14
14
  end
15
15
 
16
- opts.on("-m MATCH", "--match MATCH", "Scan pattern") do |v|
16
+ opts.on("-m MATCH", "--match MATCH", "Only scan the pattern") do |v|
17
17
  options[:match] = v
18
18
  end
19
19
 
20
+ opts.on("-l LIMIT", "--limit LIMIT", "Only show top <limit> keys") do |v|
21
+ options[:limit] = v.to_i
22
+ end
23
+
24
+ options[:detail] = false
25
+ opts.on("-d", "--detail", "Show detail info(type & size)") do |v|
26
+ options[:detail] = v
27
+ end
28
+
29
+ options[:format] = 'table'
30
+ opts.on("-t FORMAT", "--format FORMAT", "Formt(simple or talbe. default is table)") do |v|
31
+ options[:format] = v
32
+ end
33
+
20
34
  # redis client options
21
35
  # -h <hostname> Server hostname (default: 127.0.0.1).
22
36
  # -p <port> Server port (default: 6379).
@@ -1,29 +1,37 @@
1
+ require "ruby-progressbar"
2
+
1
3
  module RedisScanner
2
4
  class Engine
3
5
  def initialize(redis, options)
4
6
  @redis = redis
5
7
  @options = options
8
+ @rule = Rule.new
6
9
  end
7
10
 
8
11
  def run
9
- convert_to_sorted_array scan
12
+ result = scan
13
+ result.values.sort
10
14
  end
11
15
 
12
16
  private
13
17
 
14
- def convert_to_sorted_array(stat)
15
- stat.to_a.sort do |x, y|
16
- if x[1] == y[1]
17
- x[0] <=> y[0]
18
- else
19
- y[1] <=> x[1]
20
- end
21
- end
18
+ def create_progress_bar
19
+ total = @redis.total_keys
20
+ bar = ProgressBar.create(
21
+ title: "Keys",
22
+ format: '%a %bᗧ%i %p%% %t',
23
+ progress_mark: ' ',
24
+ remainder_mark: '・',
25
+ total: total)
26
+ bar.log "total keys is #{total}"
27
+ bar
22
28
  end
23
29
 
24
30
  def scan
25
31
  cursor = 0
26
- stat = Hash.new(0)
32
+ stat = Hash.new {|hash, key| hash[key] = Pattern.new(key) }
33
+
34
+ bar = create_progress_bar
27
35
  while true
28
36
  if @options[:match]
29
37
  cursor, result = @redis.scan cursor, match: @options[:match]
@@ -31,32 +39,22 @@ module RedisScanner
31
39
  cursor, result = @redis.scan cursor
32
40
  end
33
41
  result.each do |key|
34
- pattern = resolve_pattern(key)
35
- stat[pattern] += 1
42
+ pattern = @rule.extract_pattern(key)
43
+ if @options[:detail]
44
+ type, size = @redis.get_type_and_size(key)
45
+ stat[pattern].increment type, size
46
+ else
47
+ stat[pattern].increment
48
+ end
49
+ bar.increment
36
50
  end
37
51
  cursor = cursor.to_i
38
52
  break if cursor == 0
39
53
  end
54
+ bar.finish
55
+
40
56
  stat
41
57
  end
42
58
 
43
- PATTERNS = [
44
- [/(:\d+:)/, ":<id>:"],
45
- [/(:\w{8}-\w{4}-\w{4}-\w{4}-\w{12}:)/, ":<uuid>:"],
46
- [/(:\d{4}-\d{2}-\d{2}:)/, ":<date>:"],
47
- [/(:\d+)$/, ":<id>"],
48
- [/(:\w{8}-\w{4}-\w{4}-\w{4}-\w{12})$/, ":<uuid>"],
49
- [/(:\d{4}-\d{2}-\d{2})$/, ":<date>"]
50
- ]
51
-
52
- def resolve_pattern(key)
53
- PATTERNS.each do |pattern, replacer|
54
- if m = pattern.match(key)
55
- key = key.sub(m[1], replacer)
56
- break
57
- end
58
- end
59
- key
60
- end
61
59
  end
62
60
  end
@@ -0,0 +1,71 @@
1
+ require "terminal-table"
2
+
3
+ module RedisScanner
4
+ class Formatter
5
+ def initialize(options)
6
+ @options = options
7
+ end
8
+
9
+ def format(patterns)
10
+ if @options[:format] == "simple"
11
+ simple patterns
12
+ else
13
+ table patterns
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def touch_limit?(count)
20
+ @options[:limit] &&
21
+ @options[:limit].to_i > 0 &&
22
+ count >= @options[:limit].to_i
23
+ end
24
+
25
+ def simple(patterns)
26
+ ret = ""
27
+ count = 0
28
+ patterns.each do |pattern|
29
+ ret << pattern.to_s
30
+ ret << "\n"
31
+ count += 1
32
+ break if touch_limit?(count)
33
+ end
34
+ ret
35
+ end
36
+
37
+ def table(patterns)
38
+ with_detail = @options[:detail]
39
+ rows = []
40
+ count = 0
41
+
42
+ patterns.each do |pattern|
43
+ if with_detail
44
+ rows << [pattern.name, pattern.total, "", ""]
45
+ pattern.sorted_items.each do |item|
46
+ rows << [" > #{item.type}", item.count, item.size, item.avg_size]
47
+ end
48
+ else
49
+ rows << [pattern.name, pattern.total]
50
+ end
51
+ count += 1
52
+ break if touch_limit?(count)
53
+ end
54
+
55
+ headings = ['Key', 'Count']
56
+ if with_detail
57
+ headings << "Size"
58
+ headings << "AvgSize"
59
+ end
60
+
61
+ table = Terminal::Table.new headings: headings, rows: rows
62
+ table.align_column(1, :right)
63
+ if with_detail
64
+ table.align_column(2, :right)
65
+ table.align_column(3, :right)
66
+ end
67
+
68
+ table.to_s
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,76 @@
1
+ module RedisScanner
2
+ class PatternItem
3
+ attr_reader :type, :count, :size
4
+
5
+ def initialize(type)
6
+ @type = type
7
+ @count = 0
8
+ @size = 0
9
+ end
10
+
11
+ def increment(size)
12
+ @count += 1
13
+ @size += size
14
+ end
15
+
16
+ def <=>(other)
17
+ if @count == other.count
18
+ @type <=> other.type
19
+ else
20
+ other.count <=> @count
21
+ end
22
+ end
23
+
24
+ def avg_size
25
+ @count > 0 ? (@size * 1.0 / @count).round(2) : nil
26
+ end
27
+
28
+ def to_s
29
+ "#{type} #{count} #{size} #{avg_size}"
30
+ end
31
+ end
32
+
33
+ class Pattern
34
+ attr_reader :name
35
+ attr_accessor :total
36
+
37
+ def initialize(name)
38
+ @name = name
39
+ @total = 0
40
+ @items = Hash.new {|hash, key| hash[key] = PatternItem.new(key) }
41
+ end
42
+
43
+ def increment(type = nil, size = nil)
44
+ @total += 1
45
+ if type && size
46
+ @items[type].increment(size)
47
+ end
48
+ end
49
+
50
+ def <=>(other)
51
+ if @total == other.total
52
+ @name <=> other.name
53
+ else
54
+ other.total <=> @total
55
+ end
56
+ end
57
+
58
+ def to_a
59
+ [name, total]
60
+ end
61
+
62
+ def to_s
63
+ ret = "#{name} #{total}"
64
+ if @items.size > 0
65
+ sorted_items.each do |item|
66
+ ret << "\n #{item}"
67
+ end
68
+ end
69
+ ret
70
+ end
71
+
72
+ def sorted_items
73
+ @items.values.sort
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,54 @@
1
+ module RedisScanner
2
+ class Redis
3
+ attr_reader :client
4
+
5
+ def initialize(options)
6
+ @options = options
7
+ @client = ::Redis.new extract_redis_options(options)
8
+ end
9
+
10
+ def scan(*args)
11
+ @client.scan *args
12
+ end
13
+
14
+ # total keys for given db(default 0)
15
+ def total_keys
16
+ ret = 0
17
+ if (info = @client.info) && (str = info["db#{@options[:db].to_i}"])
18
+ if m = str.scan(/keys=(\d+)/)
19
+ ret = m.flatten.first.to_i
20
+ end
21
+ end
22
+ ret
23
+ end
24
+
25
+ def get_type_and_size(key)
26
+ type = @client.type key
27
+ size = case type
28
+ when "string"
29
+ @client.strlen key
30
+ when "list"
31
+ @client.llen key
32
+ when "hash"
33
+ @client.hlen key
34
+ when "set"
35
+ @client.scard key
36
+ when "zset"
37
+ @client.zcard key
38
+ else
39
+ 1
40
+ end
41
+ [type, size.to_i]
42
+ end
43
+
44
+ private
45
+
46
+ def extract_redis_options(options)
47
+ result = {}
48
+ [:host, :port, :socket, :password, :db].each do |key|
49
+ result[key] = options[key]
50
+ end
51
+ result
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,26 @@
1
+ module RedisScanner
2
+ class Rule
3
+ PRESET_RULES = [
4
+ [/(:\d+:)/, ":<id>:"],
5
+ [/(:\w{8}-\w{4}-\w{4}-\w{4}-\w{12}:)/, ":<uuid>:"],
6
+ [/(:\d{4}-\d{2}-\d{2}:)/, ":<date>:"],
7
+ [/(:\d+)$/, ":<id>"],
8
+ [/(:\w{8}-\w{4}-\w{4}-\w{4}-\w{12})$/, ":<uuid>"],
9
+ [/(:\d{4}-\d{2}-\d{2})$/, ":<date>"]
10
+ ]
11
+
12
+ def initialize
13
+ @rules = PRESET_RULES
14
+ end
15
+
16
+ def extract_pattern(key)
17
+ @rules.each do |rule, replacer|
18
+ if m = rule.match(key)
19
+ key = key.sub(m[1], replacer)
20
+ break
21
+ end
22
+ end
23
+ key
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module RedisScanner
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
data/lib/redis_scanner.rb CHANGED
@@ -1,32 +1,29 @@
1
1
  require "redis_scanner/version"
2
+ require "redis_scanner/rule"
3
+ require "redis_scanner/pattern"
4
+ require "redis_scanner/redis"
2
5
  require "redis_scanner/engine"
6
+ require "redis_scanner/formatter"
3
7
  require "redis"
4
8
 
5
9
  module RedisScanner
6
10
  def self.scan(options)
7
- redis = Redis.new extract_redis_options(options)
11
+ redis = Redis.new options
8
12
  engine = Engine.new redis, options
9
- result = engine.run
10
- output_result(result, options)
13
+ patterns = engine.run
14
+ output_result(patterns, options)
11
15
  end
12
16
 
13
- def self.output_result(result, options)
17
+ def self.output_result(patterns, options)
18
+ formatter = Formatter.new(options)
19
+ result = formatter.format patterns
14
20
  if options[:file]
15
21
  File.open(options[:file], "w") do |file|
16
- result.each { |key, count| file.puts "#{key} #{count}" }
22
+ file.puts result
17
23
  end
18
24
  else
19
- puts "=========result is========="
20
- result.each { |key, count| puts "#{key} #{count}" }
21
- puts "==========================="
25
+ puts result
22
26
  end
23
27
  end
24
28
 
25
- def self.extract_redis_options(options)
26
- result = {}
27
- [:host, :port, :socket, :password, :db].each do |key|
28
- result[key] = options[key]
29
- end
30
- result
31
- end
32
29
  end
@@ -19,6 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "redis"
22
+ spec.add_dependency "ruby-progressbar"
23
+ spec.add_dependency "terminal-table"
22
24
 
23
25
  spec.add_development_dependency "bundler", "~> 1.11"
24
26
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_scanner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Xie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-27 00:00:00.000000000 Z
11
+ date: 2016-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-progressbar
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: terminal-table
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: bundler
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -86,6 +114,10 @@ files:
86
114
  - bin/redis_scanner
87
115
  - lib/redis_scanner.rb
88
116
  - lib/redis_scanner/engine.rb
117
+ - lib/redis_scanner/formatter.rb
118
+ - lib/redis_scanner/pattern.rb
119
+ - lib/redis_scanner/redis.rb
120
+ - lib/redis_scanner/rule.rb
89
121
  - lib/redis_scanner/version.rb
90
122
  - redis_scanner.gemspec
91
123
  homepage: http://github.com/xiewenwei/redis_scanner.git