akainaa 0.1.1 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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[]