statsn 0.1.0
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/.gitignore +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +36 -0
- data/Rakefile +6 -0
- data/Readme.md +55 -0
- data/bin/statsn-calls +93 -0
- data/lib/statsn/version.rb +3 -0
- data/lib/statsn.rb +36 -0
- data/spec/argv.example.txt +1 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/statsn_calls_spec.rb +44 -0
- data/spec/statsn_spec.rb +69 -0
- data/statsn.gemspec +14 -0
- metadata +82 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
spec/argv.txt
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
statsn (0.1.0)
|
5
|
+
newrelic_rpm (~> 3.5)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
bump (0.3.7)
|
11
|
+
diff-lcs (1.1.3)
|
12
|
+
faraday (0.8.4)
|
13
|
+
multipart-post (~> 1.1)
|
14
|
+
json (1.7.5)
|
15
|
+
multipart-post (1.1.5)
|
16
|
+
newrelic_rpm (3.5.3.25)
|
17
|
+
rake (0.9.2)
|
18
|
+
rspec (2.6.0)
|
19
|
+
rspec-core (~> 2.6.0)
|
20
|
+
rspec-expectations (~> 2.6.0)
|
21
|
+
rspec-mocks (~> 2.6.0)
|
22
|
+
rspec-core (2.6.4)
|
23
|
+
rspec-expectations (2.6.0)
|
24
|
+
diff-lcs (~> 1.1.2)
|
25
|
+
rspec-mocks (2.6.0)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
bump
|
32
|
+
faraday
|
33
|
+
json
|
34
|
+
rake
|
35
|
+
rspec (~> 2)
|
36
|
+
statsn!
|
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
StatsN: Aggregate statistics using NewRelic's custom metrics.
|
2
|
+
|
3
|
+
Install
|
4
|
+
=======
|
5
|
+
|
6
|
+
gem install statsn
|
7
|
+
|
8
|
+
Usage
|
9
|
+
=====
|
10
|
+
|
11
|
+
### Track
|
12
|
+
|
13
|
+
require "statsn"
|
14
|
+
|
15
|
+
Statsn.increment("Custom/Xyz") -> tracked as "Custom/Xyz"
|
16
|
+
Statsn.increment([Abc::Xyz, "update"]) -> tracked as "Custom/Abc/Xyz/update"
|
17
|
+
Statsn.increment([self, "update"]) -> tracked as "Custom/<class-you-are-in>/update"
|
18
|
+
|
19
|
+
Statsn.increment("Custom/Xyz", 5) -> increment by 5
|
20
|
+
|
21
|
+
Statsn.time("Custom/Abc") do
|
22
|
+
sleep 1
|
23
|
+
end
|
24
|
+
|
25
|
+
### Analyze
|
26
|
+
|
27
|
+
# show all metrics and fields
|
28
|
+
curl -H "x-api-key:API_KEY" https://api.newrelic.com/api/v1/applications/12345/metrics.json -> list of all metrics and fields
|
29
|
+
|
30
|
+
gem install faraday json
|
31
|
+
|
32
|
+
# statsn-calls API-KEY ACCOUNT-ID APPLICATION-ID METRIC-NAME
|
33
|
+
statsn-calls asdhgjasdgjhdasjgahsdasdjghsdjhg 123 1234567 Controller/users/index
|
34
|
+
|
35
|
+
call_count: 274909.0
|
36
|
+
total_call_time: 37585.77862974794
|
37
|
+
response time: 0.1367208008095331
|
38
|
+
call per second: 3.18181712962963
|
39
|
+
|
40
|
+
# statsn-calls API-KEY ACCOUNT-ID APPLICATION-ID METRIC-NAME-PREFIX
|
41
|
+
statsn-calls asdhgjasdgjhdasjgahsdasdjghsdjhg 123 1234567 Controller/users/*
|
42
|
+
|
43
|
+
call_count: 287300.0
|
44
|
+
total_call_time: 39330.15688060733
|
45
|
+
response time: 0.13689577751690682
|
46
|
+
call per second: 3.3252314814814814
|
47
|
+
|
48
|
+
Author
|
49
|
+
======
|
50
|
+
Original version by [Morten Primdahl](https://github.com/morten)<br/>
|
51
|
+
|
52
|
+
[Michael Grosser](http://grosser.it)<br/>
|
53
|
+
michael@grosser.it<br/>
|
54
|
+
License: MIT<br/>
|
55
|
+
[](https://travis-ci.org/grosser/statsn)
|
data/bin/statsn-calls
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
raise "Needs 1.9+" if RUBY_VERSION < "1.9.0"
|
4
|
+
PERIOD = 24*60*60
|
5
|
+
API_METRICS_LIMIT = 100
|
6
|
+
|
7
|
+
def stats(options)
|
8
|
+
response = Faraday.new(
|
9
|
+
:url => "https://api.newrelic.com/api/v1/accounts/#{ACCOUNT_ID}/applications/#{APP_ID}/data.json",
|
10
|
+
:params => options,
|
11
|
+
:headers => {"x-api-key" => API_KEY}
|
12
|
+
).get
|
13
|
+
raise response.body unless response.status == 200
|
14
|
+
JSON.parse(response.body)
|
15
|
+
end
|
16
|
+
|
17
|
+
def aggregate(names, field)
|
18
|
+
names.each_slice(API_METRICS_LIMIT).map do |group|
|
19
|
+
stats(:metrics => group, :field => field, :begin => Time.now - PERIOD, :end => Time.now)
|
20
|
+
end.flatten(1)
|
21
|
+
end
|
22
|
+
|
23
|
+
def metrics
|
24
|
+
response = `curl -H "x-api-key:#{API_KEY}" https://api.newrelic.com/api/v1/applications/#{APP_ID}/metrics.json`
|
25
|
+
JSON.parse(response)
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_options
|
29
|
+
banner = nil
|
30
|
+
OptionParser.new do |opts|
|
31
|
+
banner = opts
|
32
|
+
opts.banner = <<-BANNER.gsub(" "*6, "")
|
33
|
+
Analyze newrelic data.
|
34
|
+
|
35
|
+
Usage:
|
36
|
+
statsn-calls API-KEY ACCOUNT-ID APPLICATION-ID METRIC-NAME
|
37
|
+
statsn-calls asdhgjasdgjhdasjgahsdasdjghsdjhg 123 1234567 Controller/users/index
|
38
|
+
|
39
|
+
statsn-calls API-KEY ACCOUNT-ID APPLICATION-ID METRIC-PREFIX
|
40
|
+
statsn-calls asdhgjasdgjhdasjgahsdasdjghsdjhg 123 1234567 Controller/users/*
|
41
|
+
|
42
|
+
Options:
|
43
|
+
BANNER
|
44
|
+
opts.on("-h", "--help", "Show this.") { puts opts; exit }
|
45
|
+
end.parse!
|
46
|
+
banner
|
47
|
+
end
|
48
|
+
|
49
|
+
def metric_names(namespace)
|
50
|
+
if namespace.end_with?("*")
|
51
|
+
namespace = namespace.sub("*", "")
|
52
|
+
metrics.select { |m| m["name"].start_with?(namespace) }.map { |m| m["name"] }
|
53
|
+
else
|
54
|
+
[namespace]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Dependencies
|
59
|
+
gem "json"
|
60
|
+
gem "faraday"
|
61
|
+
|
62
|
+
require "json"
|
63
|
+
require "faraday"
|
64
|
+
require "optparse"
|
65
|
+
|
66
|
+
# ARGS
|
67
|
+
options = parse_options
|
68
|
+
unless ARGV.size == 4
|
69
|
+
puts options
|
70
|
+
exit 1
|
71
|
+
end
|
72
|
+
API_KEY, ACCOUNT_ID, APP_ID, namespace = ARGV
|
73
|
+
|
74
|
+
# Data
|
75
|
+
names = metric_names(namespace)
|
76
|
+
results = ["call_count", "total_call_time"].map do |field|
|
77
|
+
metric_data = aggregate(names, field).group_by{ |x| x["name"] }
|
78
|
+
sums = metric_data.map{ |_, values| values.inject(0){ |sum,x| sum + x[field] } }.inject(:+)
|
79
|
+
[field, sums]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Display results
|
83
|
+
results = Hash[results]
|
84
|
+
if results.values.first
|
85
|
+
call_time = results["total_call_time"]
|
86
|
+
call_count = results["call_count"]
|
87
|
+
puts results.map{|x|x.join(": ")}
|
88
|
+
puts "response time: #{call_time / call_count}"
|
89
|
+
puts "call per second: #{call_count / PERIOD}"
|
90
|
+
else
|
91
|
+
puts "No data found for #{names.join(", ")} in last #{PERIOD} seconds"
|
92
|
+
exit 1
|
93
|
+
end
|
data/lib/statsn.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "statsn/version"
|
2
|
+
require "newrelic_rpm"
|
3
|
+
|
4
|
+
module Statsn
|
5
|
+
PREFIX = "Custom"
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :api_key
|
9
|
+
|
10
|
+
# Increments the count of the key by the given amount, default 1
|
11
|
+
def increment(who, amount = 1)
|
12
|
+
stat(key(who)).record_data_point(amount)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Times the given block and returns the output of the block
|
16
|
+
def time(who, &block)
|
17
|
+
trace_execution_unscoped(key(who), {}, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def stat(key)
|
23
|
+
NewRelic::Agent.agent.stats_engine.get_stats_no_scope(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def key(who)
|
27
|
+
if who.is_a?(Array)
|
28
|
+
model = who.first
|
29
|
+
model = (model.is_a?(Class) ? model.name : model.class.name).gsub("::", "/")
|
30
|
+
[PREFIX, model, *who[1..-1]].compact.join("/")
|
31
|
+
else
|
32
|
+
who
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
asdadsasddasads 123 123123
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "statsn-calls" do
|
4
|
+
let(:argv){ File.read("#{Bundler.root}/spec/argv.txt").strip }
|
5
|
+
|
6
|
+
it "gives us some numbers" do
|
7
|
+
result = run("#{argv} Controller/api/v2/users/update")
|
8
|
+
result.should =~ /^call_count: \d+/
|
9
|
+
result.should =~ /^total_call_time: \d+/
|
10
|
+
result.should =~ /^response time: \d+/
|
11
|
+
result.should =~ /^call per second: \d+/
|
12
|
+
end
|
13
|
+
|
14
|
+
it "gives us some numbers for a prefix call" do
|
15
|
+
result = run("#{argv} Controller/api/v2/users/*")
|
16
|
+
result.should =~ /^call_count: \d+/
|
17
|
+
result.should =~ /^total_call_time: \d+/
|
18
|
+
result.should =~ /^response time: \d+/
|
19
|
+
result.should =~ /^call per second: \d+/
|
20
|
+
end
|
21
|
+
|
22
|
+
it "shows help" do
|
23
|
+
result = run("--help")
|
24
|
+
result.should include("statsn-calls API-KEY")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "shows help and fail with invalid args" do
|
28
|
+
result = run("asdadsasdasd", :fail => true)
|
29
|
+
result.should include("statsn-calls API-KEY")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "tell us if the metric is missing" do
|
33
|
+
result = run("#{argv} Controller/xxxxxxx", :fail => true)
|
34
|
+
result.should include("No data found for Controller/xxxxxxx")
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def run(argv, options={})
|
40
|
+
result = `ruby -I #{Bundler.root}/lib #{Bundler.root}/bin/statsn-calls #{argv} 2>&1`
|
41
|
+
raise "FAILED: #{result}" if $?.success? == !!options[:fail]
|
42
|
+
result
|
43
|
+
end
|
44
|
+
end
|
data/spec/statsn_spec.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Foobar
|
4
|
+
class TestClass
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
describe Statsn do
|
9
|
+
it "has a VERSION" do
|
10
|
+
Statsn::VERSION.should =~ /^[\.\da-z]+$/
|
11
|
+
end
|
12
|
+
|
13
|
+
describe ".increment" do
|
14
|
+
it "calls count api with 1" do
|
15
|
+
NewRelic::MethodTraceStats.any_instance.should_receive(:record_data_point).with(1)
|
16
|
+
Statsn.increment("xxx")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "calls count api with given number" do
|
20
|
+
NewRelic::MethodTraceStats.any_instance.should_receive(:record_data_point).with(3)
|
21
|
+
Statsn.increment("xxx", 3)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe ".time" do
|
26
|
+
before do
|
27
|
+
NewRelic::Agent.is_execution_traced?.should == true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns block result" do
|
31
|
+
Statsn.time("xxx"){ 4 }.should == 4
|
32
|
+
end
|
33
|
+
|
34
|
+
it "raises errors" do
|
35
|
+
expect{
|
36
|
+
Statsn.time("xxx"){ raise EOFError }
|
37
|
+
}.to raise_error(EOFError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "records used time" do
|
41
|
+
NewRelic::MethodTraceStats.any_instance.should_receive(:trace_call).with{|x| x.should be_within(0.05).of(0.1) }
|
42
|
+
Statsn.time("xxx"){ sleep 0.1 }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe ".data" do
|
47
|
+
it "returns data for metrics" do
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe ".key" do
|
53
|
+
it "builds with a string" do
|
54
|
+
Statsn.send(:key, "xxx").should == "xxx"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "builds with a class and name" do
|
58
|
+
Statsn.send(:key, [Foobar::TestClass, "xxx"]).should == "Custom/Foobar/TestClass/xxx"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "builds with an instance and name" do
|
62
|
+
Statsn.send(:key, [Foobar::TestClass.new, "xxx"]).should == "Custom/Foobar/TestClass/xxx"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "ignores nil" do
|
66
|
+
Statsn.send(:key, [Foobar::TestClass, nil, nil, "xxx"]).should == "Custom/Foobar/TestClass/xxx"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/statsn.gemspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
name = "statsn"
|
3
|
+
require "#{name}/version"
|
4
|
+
|
5
|
+
Gem::Specification.new name, Statsn::VERSION do |s|
|
6
|
+
s.summary = "StatsN: Aggregate statistics using newrelics custom metrics"
|
7
|
+
s.authors = ["Michael Grosser"]
|
8
|
+
s.email = "michael@grosser.it"
|
9
|
+
s.homepage = "http://github.com/grosser/#{name}"
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.license = "MIT"
|
12
|
+
s.executables = ["statsn-calls"]
|
13
|
+
s.add_runtime_dependency "newrelic_rpm", "~> 3.5"
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: statsn
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Grosser
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
version_requirements: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.5'
|
20
|
+
none: false
|
21
|
+
prerelease: false
|
22
|
+
name: newrelic_rpm
|
23
|
+
requirement: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '3.5'
|
28
|
+
none: false
|
29
|
+
type: :runtime
|
30
|
+
description:
|
31
|
+
email: michael@grosser.it
|
32
|
+
executables:
|
33
|
+
- statsn-calls
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- .travis.yml
|
39
|
+
- Gemfile
|
40
|
+
- Gemfile.lock
|
41
|
+
- Rakefile
|
42
|
+
- Readme.md
|
43
|
+
- bin/statsn-calls
|
44
|
+
- lib/statsn.rb
|
45
|
+
- lib/statsn/version.rb
|
46
|
+
- spec/argv.example.txt
|
47
|
+
- spec/spec_helper.rb
|
48
|
+
- spec/statsn_calls_spec.rb
|
49
|
+
- spec/statsn_spec.rb
|
50
|
+
- statsn.gemspec
|
51
|
+
homepage: http://github.com/grosser/statsn
|
52
|
+
licenses:
|
53
|
+
- MIT
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
hash: -3608192158582391302
|
66
|
+
none: false
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
hash: -3608192158582391302
|
75
|
+
none: false
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.8.24
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: ! 'StatsN: Aggregate statistics using newrelics custom metrics'
|
82
|
+
test_files: []
|