airbrake_tools 0.0.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.
- data/.travis.yml +4 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +44 -0
- data/Rakefile +6 -0
- data/Readme.md +18 -0
- data/airbrake_tools.gemspec +12 -0
- data/lib/airbrake_tools/version.rb +3 -0
- data/lib/airbrake_tools.rb +117 -0
- data/spec/airbrake_tools_spec.rb +52 -0
- data/spec/spec_helper.rb +1 -0
- metadata +55 -0
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
airbrake_tools (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
airbrake-api (4.2.2)
|
10
|
+
faraday_middleware (~> 0.8)
|
11
|
+
hashie
|
12
|
+
mash
|
13
|
+
multi_xml
|
14
|
+
parallel (~> 0.5.0)
|
15
|
+
bump (0.3.5)
|
16
|
+
diff-lcs (1.1.3)
|
17
|
+
faraday (0.8.4)
|
18
|
+
multipart-post (~> 1.1)
|
19
|
+
faraday_middleware (0.9.0)
|
20
|
+
faraday (>= 0.7.4, < 0.9)
|
21
|
+
hashie (1.2.0)
|
22
|
+
mash (0.1.1)
|
23
|
+
multi_xml (0.5.1)
|
24
|
+
multipart-post (1.1.5)
|
25
|
+
parallel (0.5.19)
|
26
|
+
rake (0.9.2.2)
|
27
|
+
rspec (2.11.0)
|
28
|
+
rspec-core (~> 2.11.0)
|
29
|
+
rspec-expectations (~> 2.11.0)
|
30
|
+
rspec-mocks (~> 2.11.0)
|
31
|
+
rspec-core (2.11.1)
|
32
|
+
rspec-expectations (2.11.3)
|
33
|
+
diff-lcs (~> 1.1.3)
|
34
|
+
rspec-mocks (2.11.3)
|
35
|
+
|
36
|
+
PLATFORMS
|
37
|
+
ruby
|
38
|
+
|
39
|
+
DEPENDENCIES
|
40
|
+
airbrake-api (>= 4.2.2)
|
41
|
+
airbrake_tools!
|
42
|
+
bump
|
43
|
+
rake
|
44
|
+
rspec (~> 2)
|
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Power tools for Airbrake
|
2
|
+
|
3
|
+
Install
|
4
|
+
=======
|
5
|
+
|
6
|
+
gem install airbrake_tools
|
7
|
+
|
8
|
+
Usage
|
9
|
+
=====
|
10
|
+
|
11
|
+
CODE EXAMPLE
|
12
|
+
|
13
|
+
Author
|
14
|
+
======
|
15
|
+
[Jonathan Cheatham](http://github.com/jcheatham)<br/>
|
16
|
+
coaxis@gmail.com<br/>
|
17
|
+
License: MIT<br/>
|
18
|
+
[](https://travis-ci.org/jcheatham/airbrake_tools)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
name = "airbrake_tools"
|
3
|
+
require "#{name}/version"
|
4
|
+
|
5
|
+
Gem::Specification.new name, AirbrakeTools::VERSION do |s|
|
6
|
+
s.summary = "Power tools for Airbrake"
|
7
|
+
s.authors = ["Jonathan Cheatham"]
|
8
|
+
s.email = "coaxis@gmail.com"
|
9
|
+
s.homepage = "http://github.com/jcheatham/#{name}"
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.license = "MIT"
|
12
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require "airbrake_tools/version"
|
2
|
+
require "airbrake-api"
|
3
|
+
|
4
|
+
module AirbrakeTools
|
5
|
+
class << self
|
6
|
+
def cli(argv)
|
7
|
+
options = extract_options(argv)
|
8
|
+
|
9
|
+
AirbrakeAPI.account = ARGV[0]
|
10
|
+
AirbrakeAPI.auth_token = ARGV[1]
|
11
|
+
AirbrakeAPI.secure = true
|
12
|
+
|
13
|
+
if AirbrakeAPI.account.to_s.empty? || AirbrakeAPI.auth_token.to_s.empty?
|
14
|
+
puts "Usage instructions: airbrake-tools --help"
|
15
|
+
return 1
|
16
|
+
end
|
17
|
+
|
18
|
+
hot(options) || 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def hot(options)
|
22
|
+
puts "Calling hot with #{AirbrakeAPI.account}, #{AirbrakeAPI.auth_token}, #{options.inspect}"
|
23
|
+
|
24
|
+
pages = 1
|
25
|
+
|
26
|
+
errors = []
|
27
|
+
pages.times do |i|
|
28
|
+
errors.concat (AirbrakeAPI.errors(:page => i+1) || []).select{|e| e.rails_env == "production" }
|
29
|
+
end
|
30
|
+
|
31
|
+
errors = Parallel.map(errors, :in_threads => 10) do |error|
|
32
|
+
begin
|
33
|
+
notices = AirbrakeAPI.notices(error.id, :pages => 1, :raw => true).compact
|
34
|
+
print "."
|
35
|
+
[error, notices]
|
36
|
+
rescue Faraday::Error::ParsingError
|
37
|
+
$stderr.puts "Ignoring #{summary(error)}, got 500 from http://#{AirbrakeAPI.account}.airbrake.io/errors/#{error.id}"
|
38
|
+
end
|
39
|
+
end.compact
|
40
|
+
|
41
|
+
$stderr.puts
|
42
|
+
|
43
|
+
errors.sort_by{|e,n| frequency(n) }.reverse.each_with_index do |(error, notices), index|
|
44
|
+
puts "##{(index+1).to_s.ljust(2)} #{frequency(notices).to_s.rjust(8)}/hour #{error.notices_count.to_s.rjust(6)}:total #{sparkline(notices, :slots => 60, :interval => 60)}"
|
45
|
+
puts " --> id: #{error.id} -- first: #{error.created_at} -- #{error.error_class} -- #{error.error_message}"
|
46
|
+
end
|
47
|
+
|
48
|
+
return 0
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def frequency(notices)
|
54
|
+
hour = 60 * 60
|
55
|
+
sum_of_ages = notices.map { |n| Time.now - n.created_at }.inject(&:+)
|
56
|
+
average_age = sum_of_ages / notices.size
|
57
|
+
time_to_error = average_age / notices.size
|
58
|
+
rate = 1 / time_to_error
|
59
|
+
(rate * hour).round(1)
|
60
|
+
end
|
61
|
+
|
62
|
+
def summary(error)
|
63
|
+
"id:#{error.id} -- first:#{error.created_at} -- #{error.error_class} -- #{error.error_message}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def extract_options(argv)
|
67
|
+
options = {
|
68
|
+
}
|
69
|
+
OptionParser.new do |opts|
|
70
|
+
opts.banner = <<-BANNER.gsub(" "*12, "")
|
71
|
+
Get the hotest airbrake errors
|
72
|
+
|
73
|
+
Usage:
|
74
|
+
airbrake-tools subdomain token [options]
|
75
|
+
token: go to airbrake -> settings, copy your auth token
|
76
|
+
|
77
|
+
Options:
|
78
|
+
BANNER
|
79
|
+
opts.on("-h", "--help", "Show this.") { puts opts; exit }
|
80
|
+
opts.on("-v", "--version", "Show Version"){ puts "airbrake-tools #{VERSION}"; exit }
|
81
|
+
end.parse!(argv)
|
82
|
+
options
|
83
|
+
end
|
84
|
+
|
85
|
+
def run(cmd)
|
86
|
+
all = ""
|
87
|
+
puts cmd
|
88
|
+
IO.popen(cmd) do |pipe|
|
89
|
+
while str = pipe.gets
|
90
|
+
all << str
|
91
|
+
puts str
|
92
|
+
end
|
93
|
+
end
|
94
|
+
[$?.success?, all]
|
95
|
+
end
|
96
|
+
|
97
|
+
def run!(command)
|
98
|
+
raise "Command failed #{command}" unless run(command).first
|
99
|
+
end
|
100
|
+
|
101
|
+
def sparkline_data(notices, options)
|
102
|
+
last = notices.last.created_at
|
103
|
+
now = Time.now
|
104
|
+
Array.new(options[:slots]).each_with_index.map do |_, i|
|
105
|
+
slot_end = now - (i * options[:interval])
|
106
|
+
slot_start = slot_end - 1 * options[:interval]
|
107
|
+
next if last > slot_end # do not show empty lines when we actually have no data
|
108
|
+
notices.select { |n| n.created_at.between?(slot_start, slot_end) }.size
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def sparkline(notices, options)
|
113
|
+
`#{File.expand_path('../../spark.sh',__FILE__)} #{sparkline_data(notices, options).join(" ")}`.strip
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
ROOT = File.expand_path('../../', __FILE__)
|
4
|
+
|
5
|
+
describe "airbrake-tools" do
|
6
|
+
def run(command, options={})
|
7
|
+
result = `#{command} 2>&1`
|
8
|
+
message = (options[:fail] ? "SUCCESS BUT SHOULD FAIL" : "FAIL")
|
9
|
+
raise "[#{message}] #{result} [#{command}]" if $?.success? == !!options[:fail]
|
10
|
+
result
|
11
|
+
end
|
12
|
+
|
13
|
+
def airbrake_tools(args, options={})
|
14
|
+
run "#{ROOT}/bin/airbrake-tools #{args}", options
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:config) { YAML.load(File.read("spec/fixtures.yml")) }
|
18
|
+
|
19
|
+
before do
|
20
|
+
Dir.chdir ROOT
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "basics" do
|
24
|
+
it "shows its usage without arguments" do
|
25
|
+
airbrake_tools("", :fail => true).should include("Usage")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "shows its usage with -h" do
|
29
|
+
airbrake_tools("-h").should include("Usage")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "shows its usage with --help" do
|
33
|
+
airbrake_tools("--help").should include("Usage")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "shows its version with -v" do
|
37
|
+
airbrake_tools("-v").should =~ /^airbrake-tools \d+\.\d+\.\d+$/
|
38
|
+
end
|
39
|
+
|
40
|
+
it "shows its version with --version" do
|
41
|
+
airbrake_tools("-v").should =~ /^airbrake-tools \d+\.\d+\.\d+$/
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "hot" do
|
46
|
+
it "kinda works" do
|
47
|
+
output = airbrake_tools("#{config["subdomain"]} #{config["auth_token"]}")
|
48
|
+
output.should =~ /#\d+\s+\d+\.\d+\/hour\s+\d+:total/
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "airbrake_tools"
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: airbrake_tools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jonathan Cheatham
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-07 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: coaxis@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- .travis.yml
|
21
|
+
- Gemfile
|
22
|
+
- Gemfile.lock
|
23
|
+
- Rakefile
|
24
|
+
- Readme.md
|
25
|
+
- airbrake_tools.gemspec
|
26
|
+
- lib/airbrake_tools.rb
|
27
|
+
- lib/airbrake_tools/version.rb
|
28
|
+
- spec/airbrake_tools_spec.rb
|
29
|
+
- spec/spec_helper.rb
|
30
|
+
homepage: http://github.com/jcheatham/airbrake_tools
|
31
|
+
licenses:
|
32
|
+
- MIT
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.8.23
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: Power tools for Airbrake
|
55
|
+
test_files: []
|