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 ADDED
@@ -0,0 +1 @@
1
+ spec/argv.txt
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - ree
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem "bump"
5
+ gem "rake"
6
+ gem "rspec", "~>2"
7
+ gem "json"
8
+ gem "faraday"
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "bump/tasks"
3
+
4
+ task :default do
5
+ sh "rspec spec/"
6
+ end
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
+ [![Build Status](https://travis-ci.org/grosser/statsn.png)](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
@@ -0,0 +1,3 @@
1
+ module Statsn
2
+ VERSION = "0.1.0"
3
+ 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
@@ -0,0 +1,2 @@
1
+ require "bundler/setup"
2
+ require "statsn"
@@ -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
@@ -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: []