akainaa 0.1.1 → 0.1.5

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
  SHA256:
3
- metadata.gz: dc10c4417757005abc7cd862c8263c8ded74c7e732a36d804e45e5074eec10fa
4
- data.tar.gz: fdb9d8e1a6c557ea32c689b6018007946c8aefba079d2d5061ce288f1919ffc8
3
+ metadata.gz: a6602589d81aa7812d340812b17c6cc253356605aaf40fc3746aa1ab409b0d94
4
+ data.tar.gz: f4c7d56da714844abb1dc8f8a9977b3cb14065d2eec7c852e9b1aa902f212cff
5
5
  SHA512:
6
- metadata.gz: 382116ed544283e771041aa23c05d99c3563323fd19fb7ba5e5b44335e14bf129f3064ce1e2654649c6d6c284fe3d8927abe950a6653337ef4ac794d10a48674
7
- data.tar.gz: c49f13dc981530dd9c76869f4d6964b8b3bd58f4727628c565c9d1bec13f58bd44f2d2ed9a0ded5b7274d3ca0523167eeea9ab25cdb51b7e5003508bb3a8950c
6
+ metadata.gz: 21b88780fb3299dc630d3aa83dc0a7fa8cc94d486e908c54c3681c87d3ac619eef2cdf71c646f6aa71d01016b64573de338cbc09f8a8b3367f836902a74571b8
7
+ data.tar.gz: 7239bd4af6b74ee056ac78959b8220a2bebddadcc4b0bc6a21f58ad7bb5471c44f51810032b56912e3c3c4743baadaf530235b11f10930915fcdeed7bd27dc83
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.5] - 2024-09-10
4
+
5
+ - Bug fix: nested method call won't handled
6
+ - Bug fix: Empty 302 response body on /akainaa/reset
7
+ - Feature: Add `ignore_glob_patterns` option to `Akainaa.start` method
8
+ - Feature: Add `hide_not_executed_files` option to `Akainaa.start` method
9
+ - Feature: Add `online_emit` option to `Akainaa.start` method
10
+
11
+ ## [0.1.2] - 2024-05-06
12
+
13
+ - Coloring all line of the method which located on multiple lines
14
+
15
+ ## [0.1.1] - 2024-04-28
16
+
17
+ - Show the most executed file if path query isn't provided
18
+
3
19
  ## [0.1.0] - 2024-03-09
4
20
 
5
21
  - Initial release
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Akainaa (赤いなぁ)
1
+ # Akainaa (赤いなぁ、 It's red...)
2
2
 
3
- ![page view](./img/webui.png)
3
+ ![page view](./img/webui-rev02.png)
4
4
 
5
5
  Akainaa is a gem to visualize code hotspot based on Coverage.
6
6
  This gem can be used for the following purposes:
@@ -8,7 +8,7 @@ This gem can be used for the following purposes:
8
8
  - super rough code profiler: The intensity of the red background for each line is proportional to the number of times it has been executed.
9
9
  - A tool helps to understand what happened in a request: Akainaa have the reset button on Web UI, so user can easily record code execution for only one request.
10
10
 
11
- You can see actual result view from [here](https://riseshia.github.io/akainaa/isucon13-baseline.html), which recorded on [isucon13](https://github.com/isucon/isucon13/tree/main/webapp/ruby) code in one benchmark run on [ISUNARABE](https://isunarabe.org/).
11
+ You can see actual result view from [here](https://riseshia.github.io/akainaa/isucon13-baseline.html), which recorded on [isucon13](https://github.com/isucon/isucon13/tree/main/webapp/ruby) code in one benchmark run on [ISUNARABE](https://isunarabe.org/). And also [here](https://riseshia.github.io/akainaa/coverage/index.html) is the result of [simplecov-html](https://github.com/simplecov-ruby/simplecov-html)(0.12.3) from same coverage, so you can compare the result.
12
12
 
13
13
  ## Installation
14
14
 
@@ -28,7 +28,13 @@ Here is example:
28
28
  ```ruby
29
29
  require 'akainaa'
30
30
 
31
- Akainaa.start(project_dir: File.expand_path(__dir__))
31
+ Akainaa.start(
32
+ project_dir: File.expand_path(__dir__),
33
+ ignore_glob_patterns: %w[
34
+ config/application.rb
35
+ config/initializers/*_initializer.rb
36
+ ],
37
+ )
32
38
 
33
39
  require_relative 'app'
34
40
 
@@ -39,6 +45,26 @@ run App
39
45
  Boot up application, do something, and access `/akainaa`.
40
46
  It will show Web UI what and how many executed.
41
47
 
48
+ ### Enable online emit mode
49
+
50
+ Akainaa can emit coverage data which recorded in interval to the file.
51
+ This feature is intended to be used with vscode-akainaa extension.
52
+
53
+ ```ruby
54
+ Akainaa.start(
55
+ project_dir: File.expand_path(__dir__),
56
+ ignore_glob_patterns: %w[
57
+ config/application.rb
58
+ config/initializers/*_initializer.rb
59
+ ],
60
+ online_emit: {
61
+ mode: :file,
62
+ interval: 1, # seconds
63
+ output_path: '/tmp/akainaa.json',
64
+ },
65
+ )
66
+ ```
67
+
42
68
  ## Development
43
69
 
44
70
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/akainaa.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = spec.summary
13
13
  spec.homepage = "https://github.com/riseshia/akainaa"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 3.0.0"
15
+ spec.required_ruby_version = ">= 3.2.0"
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = spec.homepage
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.files = Dir.chdir(__dir__) do
24
24
  `git ls-files -z`.split("\x0").reject do |f|
25
25
  (File.expand_path(f) == __FILE__) ||
26
- f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
26
+ f.start_with?(*%w[bin/ test/ sample/ docs/ img/ .git .github Gemfile Rakefile])
27
27
  end
28
28
  end
29
29
  spec.bindir = "exe"
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Akainaa
6
+ class CallNodeVisitor < Prism::Visitor
7
+ MethodRange = Data.define(:name, :start_line, :end_line) do
8
+ def start_line_as_idx
9
+ start_line - 1
10
+ end
11
+
12
+ def method_row_range_as_idx
13
+ (start_line - 1)..(end_line - 1)
14
+ end
15
+ end
16
+
17
+ attr_reader :multiline_method_calls
18
+
19
+ def initialize
20
+ super
21
+
22
+ @multiline_method_calls = []
23
+ end
24
+
25
+ def visit_call_node(node)
26
+ if node.arguments &&
27
+ node.message_loc.start_line != node.arguments.location&.end_line
28
+
29
+ @multiline_method_calls << MethodRange.new(
30
+ name: node.message,
31
+ start_line: node.message_loc.start_line,
32
+ end_line: node.arguments.location.end_line,
33
+ )
34
+ end
35
+
36
+ super
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Akainaa
4
+ module Util
5
+ module_function
6
+
7
+ # @param [String] source_path
8
+ # @param [Array<Integer>] lines
9
+ #
10
+ # @return [Array<Integer>]
11
+ def fullfill_multiline_method_calls(source_path, lines)
12
+ result = Prism.parse_file(source_path)
13
+ prog_node = result.value
14
+
15
+ visitor = ::Akainaa::CallNodeVisitor.new
16
+ visitor.visit(prog_node)
17
+
18
+ fullfilled_lines = lines.dup
19
+
20
+ visitor.multiline_method_calls.each do |method_range|
21
+ call_count = lines[method_range.start_line_as_idx]
22
+ next if call_count.nil?
23
+
24
+ method_range.method_row_range_as_idx.each do |idx|
25
+ if fullfilled_lines[idx].nil?
26
+ fullfilled_lines[idx] = call_count
27
+ elsif fullfilled_lines[idx] < call_count
28
+ fullfilled_lines[idx] = call_count
29
+ else
30
+ # use as it is
31
+ end
32
+ end
33
+ end
34
+
35
+ fullfilled_lines
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Akainaa
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.5"
5
5
  end
data/lib/akainaa.rb CHANGED
@@ -1,31 +1,100 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'coverage'
4
+ require 'fileutils'
4
5
 
5
6
  require_relative 'akainaa/version'
7
+ require_relative 'akainaa/call_node_visitor'
8
+ require_relative 'akainaa/util'
6
9
 
7
10
  module Akainaa
8
11
  class Error < StandardError; end
9
12
 
10
13
  class << self
11
- attr_accessor :project_dir
12
-
13
- def start(project_dir:)
14
+ attr_accessor :project_dir, :ignore_files, :hide_not_executed_files
15
+
16
+ def start(
17
+ project_dir:,
18
+ ignore_glob_patterns: [],
19
+ hide_not_executed_files: false,
20
+ online_emit: nil
21
+ )
14
22
  @project_dir = project_dir
15
23
  @project_dir += '/' unless @project_dir.end_with?('/')
24
+ ignore_files = ignore_glob_patterns.flat_map do |pattern|
25
+ Dir["#{@project_dir}#{pattern}"].to_a
26
+ end
27
+ @ignore_files = Set.new(ignore_files)
28
+ @hide_not_executed_files = hide_not_executed_files
29
+ @monitor = Monitor.new
16
30
 
17
31
  Coverage.start(lines: true)
32
+
33
+ if online_emit.is_a?(Hash)
34
+ option = default_online_emit.merge(online_emit)
35
+ FileUtils.mkdir_p(File.dirname(option[:path]))
36
+ start_multipart_emit(option)
37
+ end
18
38
  end
19
39
 
20
40
  def peek_result
21
41
  Coverage
22
42
  .peek_result
23
43
  .select { |k, _v| k.start_with?(project_dir) }
44
+ .reject { |k, _v| ignore_files.member?(k) }
24
45
  .transform_keys { |k| k.sub(project_dir, '') }
25
46
  end
26
47
 
27
48
  def reset
28
- Coverage.result(stop: false, clear: true)
49
+ @monitor.synchronize do
50
+ Coverage.result(stop: false, clear: true)
51
+ @previous_result = {}
52
+ end
53
+ end
54
+
55
+ private def default_online_emit
56
+ {
57
+ mode: :file,
58
+ interval: 1,
59
+ path: 'tmp/coverage.json',
60
+ }
61
+ end
62
+
63
+ private def start_multipart_emit(option)
64
+ Thread.new do
65
+ @monitor.synchronize do
66
+ @previous_result = {}
67
+ end
68
+
69
+ loop do
70
+ sleep option[:interval]
71
+ current_result = peek_result
72
+
73
+ diff = {}
74
+ current_result.each do |path, path_coverage|
75
+ previous_path_coverage = @previous_result[path]
76
+
77
+ if previous_path_coverage.nil?
78
+ diff[path] = path_coverage
79
+ elsif previous_path_coverage[:lines].size != path_coverage[:lines].size
80
+ diff[path] = path_coverage
81
+ else
82
+ diff[path] = { lines: [] }
83
+
84
+ path_coverage[:lines].each_with_index do |count, index|
85
+ val = count ? count - previous_path_coverage[:lines][index] : nil
86
+
87
+ diff[path][:lines] << val
88
+ end
89
+ end
90
+ end
91
+
92
+ @monitor.synchronize do
93
+ @previous_result = current_result
94
+ end
95
+ File.write(option[:path], diff.to_json)
96
+ end
97
+ end
29
98
  end
30
99
  end
31
100
 
@@ -46,7 +115,7 @@ module Akainaa
46
115
  path = extract_path_from_query(env)
47
116
  Akainaa.reset
48
117
 
49
- [302, { 'Location' => "/akainaa?path=#{path}" }, [html]]
118
+ [302, { 'Location' => "/akainaa?path=#{path}" }, []]
50
119
  else
51
120
  @app.call(env)
52
121
  end
@@ -81,6 +150,8 @@ module Akainaa
81
150
 
82
151
  li_elements = files.sort.map do |file|
83
152
  total_count_on_file = coverage_result[file][:lines].reject(&:nil?).sum
153
+ next '' if Akainaa.hide_not_executed_files && total_count_on_file == 0
154
+
84
155
  count_top = (total_count_on_file * 10 / max_count_on_proj).to_i * 10
85
156
 
86
157
  class_suffix = file == current_path ? ' current' : ''
@@ -136,7 +207,7 @@ module Akainaa
136
207
  return "<" + "p>#{path} not found.<" + "/p>"
137
208
  end
138
209
 
139
- coverage_on_line = path_result[:lines]
210
+ coverage_on_line = Akainaa::Util.fullfill_multiline_method_calls(path, path_result[:lines])
140
211
  max_count_on_file = coverage_on_line.reject(&:nil?).max + 1
141
212
 
142
213
  lines = []
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: akainaa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-28 00:00:00.000000000 Z
11
+ date: 2024-09-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Minimum rack middleware for coverage
14
14
  email:
@@ -20,21 +20,11 @@ files:
20
20
  - CHANGELOG.md
21
21
  - LICENSE
22
22
  - README.md
23
- - Rakefile
24
23
  - akainaa.gemspec
25
- - docs/isucon13-baseline.html
26
- - img/webui.png
27
24
  - lib/akainaa.rb
25
+ - lib/akainaa/call_node_visitor.rb
26
+ - lib/akainaa/util.rb
28
27
  - lib/akainaa/version.rb
29
- - sample/.ruby-version
30
- - sample/Gemfile
31
- - sample/Gemfile.lock
32
- - sample/app.rb
33
- - sample/config.ru
34
- - sample/notification.rb
35
- - sample/theme.rb
36
- - sample/user.rb
37
- - sample/util.rb
38
28
  homepage: https://github.com/riseshia/akainaa
39
29
  licenses:
40
30
  - MIT
@@ -50,14 +40,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
50
40
  requirements:
51
41
  - - ">="
52
42
  - !ruby/object:Gem::Version
53
- version: 3.0.0
43
+ version: 3.2.0
54
44
  required_rubygems_version: !ruby/object:Gem::Requirement
55
45
  requirements:
56
46
  - - ">="
57
47
  - !ruby/object:Gem::Version
58
48
  version: '0'
59
49
  requirements: []
60
- rubygems_version: 3.6.0.dev
50
+ rubygems_version: 3.5.11
61
51
  signing_key:
62
52
  specification_version: 4
63
53
  summary: Minimum rack middleware for coverage
data/Rakefile DELETED
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- task default: %i[]