airbrake_tools 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- airbrake_tools (0.0.4)
4
+ airbrake_tools (0.0.5)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
@@ -9,4 +9,5 @@ Gem::Specification.new name, AirbrakeTools::VERSION do |s|
9
9
  s.homepage = "http://github.com/jcheatham/#{name}"
10
10
  s.files = `git ls-files`.split("\n")
11
11
  s.license = "MIT"
12
+ s.executables = ["airbrake-tools"]
12
13
  end
@@ -1,3 +1,3 @@
1
1
  module AirbrakeTools
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -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
- errors = hot(options)
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
- pages = (options[:pages] || DEFAULT_HOT_PAGES).to_i
40
- errors = []
41
- pages.times do |i|
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
- errors = Parallel.map(errors, :in_threads => 10) do |error|
47
- begin
48
- notices = AirbrakeAPI.notices(error.id, :pages => 1, :raw => true).compact
49
- print "."
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
- errors.sort_by{|e,n,f| f }.reverse
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.first[1][0..compare_depth]
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
- hour = 60 * 60
105
- sum_of_ages = notices.map { |n| Time.now - n.created_at }.inject(&:+)
106
- average_age = sum_of_ages / notices.size
107
- time_to_error = average_age / notices.size
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[:env] = s }
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[:env] = s }
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 }
@@ -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
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-20 00:00:00.000000000 Z
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: