builder_apm 0.4.2 → 0.5.1

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.
@@ -0,0 +1,170 @@
1
+ require 'builder_apm/doctor/openai_chat_gpt'
2
+ require 'builder_apm/doctor/bravo_chat_ai'
3
+
4
+ module BuilderApm
5
+ module Doctor
6
+ class AiDoctor
7
+ def initialize
8
+ api_key = BuilderApm.configuration.api_key
9
+ case BuilderApm.configuration.api
10
+ when "OpenAi"
11
+ @ai_client = OpenAIChatGPT.new(api_key, "gpt-3.5-turbo-16k")
12
+ else
13
+ @ai_client = BravoChatAi.new(api_key)
14
+ end
15
+ @role = "You are an AI trained to analyse and optimise web requests and database queries for a Ruby on Rails api. Your task is to identify any performance issues in the given request data, suggest possible solutions and show examples, Also show which files/methods should be looked into further"
16
+ @ai_client.role(@role)
17
+ end
18
+
19
+ def diagnose(request_data)
20
+ diagnosis = if request_data["status"] == 500
21
+ diagnose_error(reduced_backtrace(request_data), backtrace_error(request_data) )
22
+ else
23
+ diagnose_backtrace(reduced_backtrace(request_data))
24
+ end
25
+ diagnosis
26
+ end
27
+
28
+ def diagnose_error(backtrace, error)
29
+ prompt = "Please review this error and stack trace for errors and provide feedback, followed by the path/filename of the rails app file causing the error and then any other files that need further investigation
30
+ eg
31
+ <h2>{the error}</h2>
32
+ <h3>Problem found:</h3>
33
+ <p>{details for the problem found}</p>
34
+
35
+ <h3>Solutions:</h3>
36
+ <p>{list of solutions}</p>
37
+
38
+ <dl><dt>Main Error File : </dt><dd class=\"main_file\"><input type=\"checkbox\" value=\"{path/filename4.ext:[line_number]}\" name=\"deeper_dive_filename\" />{path/filename.ext:line_number }</dd></dl>
39
+ <dl><dt>Addtional Files to review:{do not include the duplicate files regardless of different line number}</dt>
40
+ <dd class=\"other_file\"><input type=\"checkbox\" value=\"{path/filename1.ext:[line_number, line_number]}\" name=\"deeper_dive_filename\" />{path/filename1.ext:[line_number, line_number]}</dd>
41
+ <dd class=\"other_file\"><input type=\"checkbox\" value=\"{path/filename2.ext:[line_number, line_number, line_number, line_number]}\" name=\"deeper_dive_filename\" />{path/filename2.ext:[line_number, line_number, line_number, line_number]}</dd>
42
+ <dd class=\"other_file\"><input type=\"checkbox\" value=\"{path/filename3.ext:[line_number, line_number]}\" name=\"deeper_dive_filename\" />{path/filename3.ext:[line_number, line_number]}</dd>
43
+ <dd class=\"other_file\"><input type=\"checkbox\" value=\"{path/filename4.ext:[line_number]}\" name=\"deeper_dive_filename\" />{path/filename4.ext:[line_number]}</dd>
44
+ </dl>
45
+
46
+ Error:
47
+ #{error}
48
+
49
+ Stack Trace:
50
+ #{backtrace}"
51
+ @ai_client.role("You are an AI trained to analyse and diagnose web requests for a Ruby on Rails api. Your task is to identify the error in the given request data, suggest possible solutions, Also show which files/methods should be looked into further")
52
+ @ai_client.chat(prompt)
53
+ end
54
+
55
+ def diagnose_backtrace(backtrace)
56
+
57
+ prompt = "Here is the request data, durations in milliseconds: \n#{backtrace}
58
+ Analyze this Stack trace and provide any potential performance issues and solutions.
59
+ The issues where more investigation into the code is required, include a list of files that are worth reviewing, unless the file path is part of gem or active_admin then skip that file and work out where in the rails app the trigger was and include that instead- path and file only (agregated, don't repeat a file even if its another line within that file).
60
+ eg
61
+ <h2>High Level Issues</h2>
62
+ <h3>{Issue type found}</h3>
63
+ <p>{times and where issue type was found}</p>
64
+ <p>{details of what this issue is and how it can effect the performance}</p>
65
+ {list of possibly solutions to fix said issue}
66
+
67
+ <h3>{Issue type found}</h3>
68
+ <p>{times and where issue type was found}</p>
69
+ <p>{details of what this issue is and how it can effect the performance}</p>
70
+ {list of possibly solutions to fix said issue}
71
+ [repeat Issue type found until no more issues type are left]
72
+
73
+
74
+ <dl><dt>Main Error File : </dt><dd class=\"main_file\"><input type=\"checkbox\" value=\"{path/filename4.ext:[line_number]}\" name=\"deeper_dive_filename\" />{path/filename.ext:line_number }</dd></dl>
75
+ <dl><dt>Addtional Files to review:{do not include the duplicate files regardless of different line number}</dt>
76
+ <dd class=\"other_file\"><input type=\"checkbox\" value=\"{path/filename1.ext:[line_number, line_number]}\" name=\"deeper_dive_filename\" />{path/filename1.ext:[line_number, line_number]}</dd>
77
+ <dd class=\"other_file\"><input type=\"checkbox\" value=\"{path/filename2.ext:[line_number, line_number, line_number, line_number]}\" name=\"deeper_dive_filename\" />{path/filename2.ext:[line_number, line_number, line_number, line_number]}</dd>
78
+ <dd class=\"other_file\"><input type=\"checkbox\" value=\"{path/filename3.ext:[line_number, line_number]}\" name=\"deeper_dive_filename\" />{path/filename3.ext:[line_number, line_number]}</dd>
79
+ <dd class=\"other_file\"><input type=\"checkbox\" value=\"{path/filename4.ext:[line_number]}\" name=\"deeper_dive_filename\" />{path/filename4.ext:[line_number]}</dd>
80
+ </dl>
81
+
82
+ "
83
+
84
+ @ai_client.chat(prompt)
85
+ end
86
+
87
+ def deeper_analysis(request_data, diagnosis, files)
88
+ targeted_code = files.map do |file_n_line|
89
+ parts = file_n_line&.split(':')
90
+ next if parts&.length < 2
91
+
92
+ file = parts.first
93
+ line = parts.last.to_i
94
+ extract_lines_from_file(file, line)
95
+ end
96
+ messages = [
97
+ {
98
+ role: "user",
99
+ content: "Here is the request data, durations in milliseconds: \n#{reduced_backtrace(request_data)}"
100
+ },
101
+ {
102
+ role: "assistant",
103
+ content: diagnosis
104
+ },
105
+ {
106
+ role: "user",
107
+ content: "this is the code block mentioned in the list of files:
108
+ #{targeted_code.join("")}
109
+
110
+ #{deep_anaysis_call_to_action(request_data)}, no solution should include altering files that have a path folder called gems, As I am unable to edit those files
111
+ eg
112
+ <p>{description of what the fix is}</p>
113
+
114
+ <pre>{code fix 1}</pre>
115
+ <p>{notes / comments}</p>
116
+ [repeat for each code fix presented]"
117
+ },
118
+ ]
119
+
120
+ @ai_client.chat(messages)
121
+ end
122
+
123
+ private
124
+
125
+ def deep_anaysis_call_to_action(request_data)
126
+ return "Provide a code fix for this issue" unless request_data["status"] == 500
127
+
128
+ "Exception Error:
129
+ #{request_data["exception_message"]}
130
+
131
+ Exception Backtrace:
132
+ #{request_data["exception_backtrace"]}
133
+ Provide a code fix for this Exception Error
134
+ "
135
+ end
136
+
137
+ def extract_lines_from_file(file_path, target_line)
138
+ # debugger
139
+ return [] unless File.exist?(file_path)
140
+
141
+ start_line = [target_line - 20, 1].max
142
+ end_line = target_line + 19
143
+
144
+ extracted_lines = ["
145
+ This is the file reported as having an issue #{file_path} on line #{target_line}.\n",
146
+ "##{file_path}\n"]
147
+
148
+ File.open(file_path, 'r') do |file|
149
+ file.each_line.drop(start_line - 1).take(end_line - start_line + 1).each_with_index do |line, index|
150
+ extracted_lines << "Line #{start_line + index}: #{line}"
151
+ end
152
+ end
153
+
154
+ extracted_lines.join("")
155
+ end
156
+
157
+ def backtrace_error(request_data)
158
+ {
159
+ exception_message: request_data["exception_message"],
160
+ exception_backtrace: request_data["exception_backtrace"]
161
+ }.to_s
162
+ end
163
+
164
+ def reduced_backtrace(request_data)
165
+ reducer = BuilderApm::Doctor::BacktraceReducer.new
166
+ reducer.reduce_backtrace(data: request_data)
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,104 @@
1
+ require 'json'
2
+ require 'digest'
3
+
4
+ module BuilderApm
5
+ module Doctor
6
+ class BacktraceReducer
7
+ TOKEN_LIMIT = 10500
8
+
9
+ def initialize
10
+ @token_count = 0
11
+ @ref_count = 0
12
+ end
13
+
14
+ def reduce_method(method)
15
+ return nil if over_limit?
16
+
17
+ reduced_method = build_reduced_method(method)
18
+ return nil if reduced_method["dur"] < 1 && reduced_method["dur"] > 0
19
+
20
+ increment_token_count(reduced_method.to_json)
21
+
22
+ process_sql_events(method, reduced_method)
23
+ process_children(method, reduced_method)
24
+
25
+ reduced_method
26
+ end
27
+
28
+ def build_reduced_method(method)
29
+ {
30
+ "dur" => method.fetch("duration", 0.0).to_f.round(3),
31
+ "method" => method.fetch("method", nil),
32
+ "line" => method.fetch("method_line", nil),
33
+ "trigger" => method.fetch("triggering_line", nil),
34
+ "sql" => [],
35
+ "children" => []
36
+ }
37
+ end
38
+
39
+ def process_sql_events(method, reduced_method)
40
+ method.fetch("sql_events", []).each do |event|
41
+ sql_event = reduce_event(event)
42
+ return if over_limit? || (sql_event["dur"] < 1 && sql_event["dur"] > 0)
43
+
44
+ reduced_method["sql"] << sql_event
45
+ increment_token_count(sql_event.to_s)
46
+ end
47
+ end
48
+
49
+ def process_children(method, reduced_method)
50
+ method.fetch("children", []).each do |child|
51
+ reduced_child = reduce_method(child)
52
+ return if over_limit?
53
+
54
+ reduced_method["children"] << reduced_child unless reduced_child.nil?
55
+ end
56
+ end
57
+
58
+ def reduce_event(event)
59
+ params = event.fetch("params", [])
60
+ params = short_hash(params) if params.size > 3
61
+ # sql = truncate_string(event.fetch("sql", ""), 400)
62
+ sql = truncate_string(event.fetch("sql", "").gsub("\"", ""), 400)
63
+
64
+ {
65
+ "dur" => event.fetch("duration", 0.0).to_f.round(3),
66
+ "sql" => sql,
67
+ "params" => params,
68
+ "records" => event.fetch("record_count", 0),
69
+ "trigger" => event.fetch("triggering_line", nil)
70
+ }
71
+ end
72
+
73
+ def reduce_json(json)
74
+ json.map { |method| reduce_method(method) }.compact
75
+ end
76
+
77
+ def short_hash(input_string)
78
+ Digest::MD5.hexdigest(input_string.join(','))[0...8]
79
+ end
80
+
81
+ def truncate_string(str, length)
82
+ str.length > length ? str[0...length] + "..." : str
83
+ end
84
+
85
+ def count_tokens(string)
86
+ tokens = string.scan(/[\w']+|[.,!?;:]/)
87
+ tokens = tokens.flat_map { |token| token.split(/(?<=[.,!?;:])$/) }
88
+ tokens.length + 17
89
+ end
90
+
91
+ def increment_token_count(string)
92
+ @token_count += count_tokens(string)
93
+ end
94
+
95
+ def over_limit?
96
+ @token_count + @ref_count > TOKEN_LIMIT
97
+ end
98
+
99
+ def reduce_backtrace(data: {})
100
+ reduce_json(data["stack"]).to_s
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,85 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+
5
+ module BuilderApm
6
+ module Doctor
7
+ class BravoChatAi
8
+ MAX_RETRIES = 3
9
+ TIMEOUT_IN_SECONDS = 300 # equivalent to 5 minutes
10
+
11
+ def initialize(api_key)
12
+ @api_key = api_key
13
+ @uri = URI.parse("https://models-gateway.builder.ai/api/v1/openai/deployments/gpt-35-turbo/chat/completions")
14
+ @role = "You are to be my assistant"
15
+ @temperature = 0.2
16
+ end
17
+
18
+ def role(ai_role = nil)
19
+ @role = ai_role unless ai_role.nil?
20
+
21
+ @role
22
+ end
23
+
24
+ def temperature(temp = nil)
25
+ @temperature = temp unless temp.nil?
26
+
27
+ @temperature
28
+ end
29
+
30
+ def chat(messages)
31
+ request = Net::HTTP::Post.new(@uri)
32
+ request.content_type = "application/json"
33
+ request["api-key"] = @api_key
34
+ request.body = JSON.dump({
35
+ "temperature" => temperature,
36
+ "messages" => construct_messages(messages)
37
+ })
38
+
39
+ req_options = {
40
+ use_ssl: @uri.scheme == "https",
41
+ }
42
+
43
+ begin
44
+ retries ||= 0
45
+ response = Net::HTTP.start(@uri.hostname, @uri.port, req_options) do |http|
46
+ http.read_timeout = TIMEOUT_IN_SECONDS
47
+ http.request(request)
48
+ end
49
+ rescue Net::ReadTimeout => e
50
+ if retries < MAX_RETRIES
51
+ retries += 1
52
+ retry
53
+ else
54
+ raise e
55
+ end
56
+ end
57
+
58
+ begin
59
+ JSON.parse(response.body)["choices"].first["message"]["content"]
60
+ rescue => e
61
+ error = JSON.parse(response.body)["error"]
62
+ output = error["message"]
63
+ output += "\n\nThis file is too large and should be looked at breaking it down into smaller files / services" if error["code"] == "context_length_exceeded"
64
+
65
+ output
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def construct_messages(messages)
72
+ messages_array = case messages
73
+ when String
74
+ [{"role" => "user", "content" => messages}]
75
+ when Array
76
+ messages.map { |hash| hash.transform_keys(&:to_s) }
77
+ else
78
+ raise ArgumentError, "Unsupported message format"
79
+ end
80
+
81
+ [{"role" => "system", "content" => role}] + messages_array
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,84 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+
5
+ module BuilderApm
6
+ module Doctor
7
+ class OpenAIChatGPT
8
+
9
+ MAX_RETRIES = 3
10
+ TIMEOUT_IN_SECONDS = 300 # equivalent to 5 minutes
11
+
12
+ def initialize(api_key, model = "gpt-3.5-turbo")
13
+ @api_key = api_key
14
+ @uri = URI.parse("https://api.openai.com/v1/chat/completions")
15
+ @role = "You are to be my assistant"
16
+ @model = model
17
+ @temperature = 0.2
18
+ end
19
+
20
+ def role(ai_role = nil)
21
+ @role = ai_role unless ai_role.nil?
22
+
23
+ @role
24
+ end
25
+
26
+ def temperature(temp = nil)
27
+ @temperature = temp unless temp.nil?
28
+
29
+ @temperature
30
+ end
31
+
32
+ def chat(messages)
33
+ request = Net::HTTP::Post.new(@uri)
34
+ request.content_type = "application/json"
35
+ request["Authorization"] = "Bearer #{@api_key}"
36
+
37
+ request.body = JSON.dump({
38
+ "model" => @model,
39
+ "temperature" => temperature,
40
+ "messages" => construct_messages(messages)
41
+ })
42
+
43
+ req_options = {
44
+ use_ssl: @uri.scheme == "https",
45
+ }
46
+
47
+ begin
48
+ retries ||= 0
49
+ response = Net::HTTP.start(@uri.hostname, @uri.port, req_options) do |http|
50
+ http.read_timeout = TIMEOUT_IN_SECONDS
51
+ http.request(request)
52
+ end
53
+ rescue Net::ReadTimeout => e
54
+ if retries < MAX_RETRIES
55
+ retries += 1
56
+ retry
57
+ else
58
+ raise e
59
+ end
60
+ end
61
+
62
+ data = JSON.parse(response.body)["choices"]
63
+
64
+ raise data.dig("error", "message") if data.is_a?(Hash) && data.dig("error", "message")
65
+
66
+ data.dig(0, "message", "content")
67
+ end
68
+
69
+ private
70
+ def construct_messages(messages)
71
+ messages_array = case messages
72
+ when String
73
+ [{"role" => "user", "content" => messages}]
74
+ when Array
75
+ messages.map { |hash| hash.transform_keys(&:to_s) }
76
+ else
77
+ raise ArgumentError, "Unsupported message format"
78
+ end
79
+
80
+ [{"role" => "system", "content" => role}] + messages_array
81
+ end
82
+ end
83
+ end
84
+ end
@@ -2,6 +2,7 @@ module BuilderApm
2
2
  module Methods
3
3
  class Instrumenter
4
4
  def initialize(root_path: Rails.root.to_s)
5
+ @this_gem_path = File.expand_path("../../..", __dir__)
5
6
  @root_path = root_path
6
7
  @call_times = {}
7
8
  end
@@ -17,28 +18,54 @@ module BuilderApm
17
18
 
18
19
  def setup_trace
19
20
  me = self
20
- TracePoint.new(:call, :return) do |tp|
21
+ TracePoint.new(:call, :return, :end, :raise) do |tp|
22
+ starttime = Time.now.to_f * 1000
21
23
  me.process_trace_point(tp) if me.valid_trace_point?(tp)
24
+ duration = (Time.now.to_f * 1000) - starttime
25
+
26
+ Thread.current["method_tracing"] = (Thread.current["method_tracing"] ||= 0) + duration
22
27
  end
23
28
  end
24
29
 
25
30
  def valid_trace_point?(tp)
26
- !Thread.current[:request_id].nil? && tp.path.start_with?(@root_path) && !tp.path.include?('controller.rb')
31
+ return !Thread.current[:request_id].nil? && tp.path.start_with?(@root_path)
32
+
33
+ # !Thread.current[:request_id].nil? && not_controller_endpoint(tp) && valid_gem_path(tp)
34
+ end
35
+
36
+ def not_controller_endpoint(tp)
37
+ start_controller = Thread.current[:stack]&.first || nil
38
+
39
+ return false unless start_controller
40
+
41
+ "#{tp.defined_class}##{tp.method_id}" != start_controller[:method]
42
+ end
43
+
44
+ def valid_gem_path(tp)
45
+ not_this_gem = !tp.path.start_with?(@this_gem_path)
46
+ is_a_tracked_gem = tp.path.split(File::SEPARATOR).any? { |folder| folder.start_with?(*gems_to_track) }
47
+ is_a_rails_app_file = tp.path.start_with?(@root_path)
48
+
49
+ not_this_gem && (is_a_tracked_gem || is_a_rails_app_file)
27
50
  end
28
51
 
29
52
  def process_trace_point(tp)
30
- if tp.event == :call
53
+ if tp.event == :call || tp.event == :b_call || tp.event == :c_call
31
54
  process_call_event(tp)
32
- elsif tp.event == :return
55
+ else
33
56
  process_return_event(tp)
34
57
  end
35
58
  end
36
59
 
37
60
  private
38
61
 
62
+ def gems_to_track
63
+ @gems_to_track = BuilderApm.configuration.gems_to_track
64
+ end
65
+
39
66
  def process_call_event(tp)
40
67
  method_id = "#{tp.defined_class}##{tp.method_id}"
41
- @call_times[method_id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
68
+ (@call_times[method_id]||= []) << Process.clock_gettime(Process::CLOCK_MONOTONIC)
42
69
  caller_info = caller_locations(4,1).first
43
70
  calling_file_path = caller_info.absolute_path
44
71
  calling_line_number = caller_info.lineno
@@ -59,7 +86,7 @@ module BuilderApm
59
86
  method_id = "#{tp.defined_class}##{tp.method_id}"
60
87
 
61
88
  if @call_times.key?(method_id)
62
- elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @call_times[method_id]
89
+ elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @call_times[method_id].pop
63
90
  elapsed_time_in_ms = (elapsed_time * 1000).round(3)
64
91
  @call_times.delete(method_id)
65
92
 
@@ -12,6 +12,8 @@ module BuilderApm
12
12
  Thread.current[:n_plus_one_duration] = 0
13
13
  Thread.current[:has_n_plus_one] = false
14
14
  Thread.current[:db_runtime] = 0
15
+ Thread.current[:method_tracing] = 0
16
+ Thread.current[:db_tracing] = 0
15
17
  start_time = Time.now.to_f * 1000
16
18
 
17
19
  @status, @headers, @response = @app.call(env)
@@ -36,6 +38,8 @@ module BuilderApm
36
38
  Thread.current[:db_runtime] = nil
37
39
  Thread.current[:stack] = nil
38
40
  Thread.current[:sql_event_id] = nil
41
+ Thread.current[:method_tracing] = nil
42
+ Thread.current[:db_tracing] = nil
39
43
  end
40
44
 
41
45
  def handle_timing(start_time, end_time, request_id)
@@ -53,6 +57,8 @@ module BuilderApm
53
57
  data[:stack][0][:start_time] = start_time
54
58
  data[:stack][0][:end_time] = end_time
55
59
  data[:stack][0][:duration] = end_time - start_time
60
+ data[:method_tracing] = Thread.current[:method_tracing]
61
+ data[:db_tracing] = Thread.current[:db_tracing]
56
62
 
57
63
  save_to_redis(data)
58
64
  end
@@ -6,8 +6,20 @@ module BuilderApm
6
6
  end
7
7
 
8
8
  def subscribe_to_notifications
9
- ActiveSupport::Notifications.subscribe('sql.active_record') { |*args| handle_sql_active_record(*args) }
10
- ActiveSupport::Notifications.subscribe('instantiation.active_record') { |*args| handle_instantiation_active_record(*args) }
9
+ ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
10
+ starttime = Time.now.to_f * 1000
11
+ handle_sql_active_record(*args)
12
+ duration = (Time.now.to_f * 1000) - starttime
13
+
14
+ Thread.current[:db_tracing] = (Thread.current[:db_tracing] ||= 0) + duration
15
+ end
16
+ ActiveSupport::Notifications.subscribe('instantiation.active_record') do |*args|
17
+ starttime = Time.now.to_f * 1000
18
+ handle_instantiation_active_record(*args)
19
+ duration = (Time.now.to_f * 1000) - starttime
20
+
21
+ Thread.current[:db_tracing] = (Thread.current[:db_tracing] ||= 0) + duration
22
+ end
11
23
  end
12
24
 
13
25
  private
@@ -111,7 +123,7 @@ module BuilderApm
111
123
 
112
124
  # If any N+1 issue is found, set 'has_n_plus_one' to true and return
113
125
  queries_count.each do |_, value|
114
- if value[:count] > 1
126
+ if value[:count] > 2
115
127
  Thread.current[:has_n_plus_one] = true
116
128
  return
117
129
  end
@@ -1,3 +1,3 @@
1
1
  module BuilderApm
2
- VERSION = "0.4.2"
2
+ VERSION = "0.5.1"
3
3
  end
@@ -1,6 +1,9 @@
1
1
  BuilderApm.configure do |config|
2
- # config.redis_url = 'redis://localhost:6379/0'
3
- # config.enable_controller_profiler = true
4
- # config.enable_active_record_profiler = true
5
- # config.enable_methods_profiler = true
2
+ config.redis_url = 'redis://localhost:6379/0'
3
+ config.enable_controller_profiler = true
4
+ config.enable_active_record_profiler = true
5
+ config.enable_methods_profiler = true
6
+ # config.api_key = ENV["OPENAI_API_KEY"]
7
+ # config.api = ENV["API_AI"] # "Bravo" - internal Ai | "OpenAi" - use openai ChatGPT
8
+ config.gems_to_track = ["bx_block_"] #partial and full gem names can be used.
6
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: builder_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Ketelle
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-28 00:00:00.000000000 Z
11
+ date: 2023-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -55,10 +55,10 @@ files:
55
55
  - ".rspec"
56
56
  - Gemfile
57
57
  - Gemfile.lock
58
- - README.md
59
58
  - Rakefile
60
59
  - app/controllers/builder_apm/application_controller.rb
61
60
  - app/controllers/builder_apm/dashboard_controller.rb
61
+ - app/controllers/builder_apm/diagnose_request_controller.rb
62
62
  - app/controllers/builder_apm/error_requests_controller.rb
63
63
  - app/controllers/builder_apm/n_plus_one_controller.rb
64
64
  - app/controllers/builder_apm/recent_requests_controller.rb
@@ -96,6 +96,10 @@ files:
96
96
  - lib/builder_apm.rb
97
97
  - lib/builder_apm/configuration.rb
98
98
  - lib/builder_apm/controllers/instrumenter.rb
99
+ - lib/builder_apm/doctor/ai_doctor.rb
100
+ - lib/builder_apm/doctor/backtrace_reducer.rb
101
+ - lib/builder_apm/doctor/bravo_chat_ai.rb
102
+ - lib/builder_apm/doctor/openai_chat_gpt.rb
99
103
  - lib/builder_apm/engine.rb
100
104
  - lib/builder_apm/methods/instrumenter.rb
101
105
  - lib/builder_apm/middleware/timing.rb
@@ -126,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
130
  - !ruby/object:Gem::Version
127
131
  version: '0'
128
132
  requirements: []
129
- rubygems_version: 3.0.9
133
+ rubygems_version: 3.1.0
130
134
  signing_key:
131
135
  specification_version: 4
132
136
  summary: Write a short summary, because RubyGems requires one.
data/README.md DELETED
@@ -1,29 +0,0 @@
1
- # BuilderApm
2
-
3
-
4
-
5
- ## Installation
6
-
7
- Add this line to your application's Gemfile:
8
-
9
- ```ruby
10
- gem 'builder_apm'
11
- ```
12
-
13
- And then execute:
14
-
15
- $ bundle install
16
-
17
- Or install it yourself as:
18
-
19
- $ gem install builder_apm
20
-
21
- run
22
- ```
23
- rails generate builder_apm:install
24
- ```
25
-
26
- ## Usage
27
-
28
- TODO: Write usage instructions here
29
-