airbrake_tools 0.0.4 → 0.0.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.
- data/Gemfile.lock +1 -1
- data/airbrake_tools.gemspec +1 -0
- data/lib/airbrake_tools/version.rb +1 -1
- data/lib/airbrake_tools.rb +47 -28
- data/spec/airbrake_tools_spec.rb +41 -0
- metadata +4 -3
data/Gemfile.lock
CHANGED
data/airbrake_tools.gemspec
CHANGED
data/lib/airbrake_tools.rb
CHANGED
@@ -4,6 +4,7 @@ require "airbrake-api"
|
|
4
4
|
|
5
5
|
module AirbrakeTools
|
6
6
|
DEFAULT_HOT_PAGES = 1
|
7
|
+
DEFAULT_NEW_PAGES = 1
|
7
8
|
DEFAULT_SUMMARY_PAGES = 5
|
8
9
|
DEFAULT_COMPARE_DEPTH = 7
|
9
10
|
DEFAULT_ENVIRONMENT = "production"
|
@@ -23,37 +24,31 @@ module AirbrakeTools
|
|
23
24
|
|
24
25
|
case ARGV[2]
|
25
26
|
when "hot"
|
26
|
-
|
27
|
-
print_errors(errors)
|
27
|
+
print_errors(hot(options))
|
28
28
|
when "list"
|
29
29
|
list(options)
|
30
30
|
when "summary"
|
31
31
|
summary(ARGV[3] || raise("Need error id"), options)
|
32
|
+
when "new"
|
33
|
+
print_errors(new(options))
|
32
34
|
else
|
33
|
-
raise "Unknown command try hot/list/summary"
|
35
|
+
raise "Unknown command #{ARGV[2].inspect} try hot/new/list/summary"
|
34
36
|
end
|
35
37
|
return 0
|
36
38
|
end
|
37
39
|
|
38
40
|
def hot(options = {})
|
39
|
-
|
40
|
-
errors
|
41
|
-
|
42
|
-
errors.concat(AirbrakeAPI.errors(:page => i+1) || [])
|
43
|
-
end
|
44
|
-
select_env!(errors, options)
|
41
|
+
errors = errors_with_notices({:pages => DEFAULT_HOT_PAGES}.merge(options))
|
42
|
+
errors.sort_by{|e,n,f| f }.reverse
|
43
|
+
end
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
[error, notices, frequency(notices)]
|
51
|
-
rescue Faraday::Error::ParsingError
|
52
|
-
$stderr.puts "Ignoring #{hot_summary(error)}, got 500 from http://#{AirbrakeAPI.account}.airbrake.io/errors/#{error.id}"
|
53
|
-
end
|
54
|
-
end.compact
|
45
|
+
def new(options = {})
|
46
|
+
errors = errors_with_notices({:pages => DEFAULT_NEW_PAGES}.merge(options))
|
47
|
+
errors.sort_by{|e,n,f| e.created_at }.reverse
|
48
|
+
end
|
55
49
|
|
56
|
-
|
50
|
+
def errors_with_notices(options)
|
51
|
+
add_notices_to_pages(errors_from_pages(options))
|
57
52
|
end
|
58
53
|
|
59
54
|
def list(options)
|
@@ -77,7 +72,11 @@ module AirbrakeTools
|
|
77
72
|
puts "last day: #{sparkline(notices, :slots => 24, :interval => 60 * 60)}"
|
78
73
|
|
79
74
|
backtraces = notices.compact.select{|n| n.backtrace }.group_by do |notice|
|
80
|
-
notice.backtrace.
|
75
|
+
if notice.backtrace.is_a?(String) # no backtrace recorded...
|
76
|
+
[]
|
77
|
+
else
|
78
|
+
notice.backtrace.first[1][0..compare_depth]
|
79
|
+
end
|
81
80
|
end
|
82
81
|
|
83
82
|
backtraces.sort_by{|_,notices| notices.size }.reverse.each_with_index do |(backtrace, notices), index|
|
@@ -90,6 +89,27 @@ module AirbrakeTools
|
|
90
89
|
|
91
90
|
private
|
92
91
|
|
92
|
+
def add_notices_to_pages(errors)
|
93
|
+
Parallel.map(errors, :in_threads => 10) do |error|
|
94
|
+
begin
|
95
|
+
notices = AirbrakeAPI.notices(error.id, :pages => 1, :raw => true).compact
|
96
|
+
print "."
|
97
|
+
[error, notices, frequency(notices)]
|
98
|
+
rescue Faraday::Error::ParsingError
|
99
|
+
$stderr.puts "Ignoring #{hot_summary(error)}, got 500 from http://#{AirbrakeAPI.account}.airbrake.io/errors/#{error.id}"
|
100
|
+
end
|
101
|
+
end.compact
|
102
|
+
end
|
103
|
+
|
104
|
+
def errors_from_pages(options)
|
105
|
+
errors = []
|
106
|
+
options[:pages].times do |i|
|
107
|
+
errors.concat(AirbrakeAPI.errors(:page => i+1) || [])
|
108
|
+
end
|
109
|
+
select_env!(errors, options)
|
110
|
+
errors
|
111
|
+
end
|
112
|
+
|
93
113
|
def select_env!(errors, options)
|
94
114
|
errors.select!{|e| e.rails_env == options[:env] || DEFAULT_ENVIRONMENT }
|
95
115
|
end
|
@@ -100,13 +120,12 @@ module AirbrakeTools
|
|
100
120
|
end
|
101
121
|
end
|
102
122
|
|
123
|
+
# we only have a limited sample size, so we do not know how many errors occurred in total
|
103
124
|
def frequency(notices)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
rate = 1 / time_to_error
|
109
|
-
(rate * hour).round(1)
|
125
|
+
return 0 if notices.empty?
|
126
|
+
range = Time.now.to_f - notices.map{ |n| n.created_at.to_f }.min
|
127
|
+
errors_per_second = notices.size / range
|
128
|
+
(errors_per_second * 60 * 60).round(2) # errors_per_hour
|
110
129
|
end
|
111
130
|
|
112
131
|
def hot_summary(error)
|
@@ -129,8 +148,8 @@ module AirbrakeTools
|
|
129
148
|
|
130
149
|
Options:
|
131
150
|
BANNER
|
132
|
-
opts.on("-c NUM", "--compare-depth NUM", Integer, "How deep to compare backtraces in summary (default: #{DEFAULT_COMPARE_DEPTH})") {|s| options[:
|
133
|
-
opts.on("-p NUM", "--pages NUM", Integer, "How maybe pages to iterate over (default: hot:#{DEFAULT_HOT_PAGES} summary:#{DEFAULT_SUMMARY_PAGES})") {|s| options[:
|
151
|
+
opts.on("-c NUM", "--compare-depth NUM", Integer, "How deep to compare backtraces in summary (default: #{DEFAULT_COMPARE_DEPTH})") {|s| options[:compare_depth] = s }
|
152
|
+
opts.on("-p NUM", "--pages NUM", Integer, "How maybe pages to iterate over (default: hot:#{DEFAULT_HOT_PAGES} new:#{DEFAULT_NEW_PAGES} summary:#{DEFAULT_SUMMARY_PAGES})") {|s| options[:pages] = s }
|
134
153
|
opts.on("-e ENV", "--environment ENV", String, "Only show errors from this environment (default: #{DEFAULT_ENVIRONMENT})") {|s| options[:env] = s }
|
135
154
|
opts.on("-h", "--help", "Show this.") { puts opts; exit }
|
136
155
|
opts.on("-v", "--version", "Show Version"){ puts "airbrake-tools #{VERSION}"; exit }
|
data/spec/airbrake_tools_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'airbrake_tools'
|
2
3
|
|
3
4
|
ROOT = File.expand_path('../../', __FILE__)
|
4
5
|
|
@@ -64,5 +65,45 @@ describe "airbrake-tools" do
|
|
64
65
|
output.should include("last 2 hours: ")
|
65
66
|
end
|
66
67
|
end
|
68
|
+
|
69
|
+
describe "newest" do
|
70
|
+
it "kinda works" do
|
71
|
+
output = airbrake_tools("#{config["subdomain"]} #{config["auth_token"]} new")
|
72
|
+
output.should =~ /#\d+\s+\d+\.\d+\/hour\s+total:\d+/
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe ".extract_options" do
|
77
|
+
it "finds nothing" do
|
78
|
+
AirbrakeTools.send(:extract_options, []).should == {}
|
79
|
+
end
|
80
|
+
|
81
|
+
it "finds pages" do
|
82
|
+
AirbrakeTools.send(:extract_options, ["--pages", "1"]).should == {:pages => 1}
|
83
|
+
end
|
84
|
+
|
85
|
+
it "finds env" do
|
86
|
+
AirbrakeTools.send(:extract_options, ["--environment", "xx"]).should == {:env => "xx"}
|
87
|
+
end
|
88
|
+
|
89
|
+
it "finds compare-depth" do
|
90
|
+
AirbrakeTools.send(:extract_options, ["--compare-depth", "1"]).should == {:compare_depth => 1}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe ".frequency" do
|
95
|
+
it "calculates for 0" do
|
96
|
+
AirbrakeTools.send(:frequency, []).should == 0
|
97
|
+
end
|
98
|
+
|
99
|
+
it "calculates for 1" do
|
100
|
+
AirbrakeTools.send(:frequency, [stub(:created_at => Time.now - (60*60))]).should == 1
|
101
|
+
end
|
102
|
+
|
103
|
+
it "calculates for n" do
|
104
|
+
# 3 per minute => 180/hour
|
105
|
+
AirbrakeTools.send(:frequency, [stub(:created_at => Time.now-60), stub(:created_at => Time.now-40), stub(:created_at => Time.now-20)]).should == 180
|
106
|
+
end
|
107
|
+
end
|
67
108
|
end
|
68
109
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: airbrake_tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,12 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-27 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description:
|
15
15
|
email: coaxis@gmail.com
|
16
|
-
executables:
|
16
|
+
executables:
|
17
|
+
- airbrake-tools
|
17
18
|
extensions: []
|
18
19
|
extra_rdoc_files: []
|
19
20
|
files:
|