profit 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html class="no-js">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6
+ <title></title>
7
+ <meta name="description" content="">
8
+ <meta name="viewport" content="width=device-width">
9
+
10
+ <link rel="stylesheet" href="css/bootstrap.min.css">
11
+ <style>
12
+ body {
13
+ padding-top: 60px;
14
+ padding-bottom: 40px;
15
+ }
16
+ </style>
17
+ <link rel="stylesheet" href="css/bootstrap-responsive.min.css">
18
+ <link rel="stylesheet" href="css/main.css">
19
+
20
+ <!--[if lt IE 9]>
21
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
22
+ <script>window.html5 || document.write('<script src="js/vendor/html5shiv.js"><\/script>')</script>
23
+ <![endif]-->
24
+ </head>
25
+ <body>
26
+ <script type="text/javascript">
27
+ window.data = <%= data.to_json %>;
28
+ </script>
29
+ <div class="navbar navbar-inverse navbar-fixed-top">
30
+ <div class="navbar-inner">
31
+ <div class="container">
32
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
33
+ <span class="icon-bar"></span>
34
+ <span class="icon-bar"></span>
35
+ <span class="icon-bar"></span>
36
+ </a>
37
+ <a class="brand" href="#">Profit Charts</a>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ <div class="tabbable tabs-left">
42
+ <ul id="key_links" class="nav nav-tabs">
43
+ <% data.keys.each_with_index do |key, index| %>
44
+ <% first = index == 0 %>
45
+ <li class="<%= 'active' if first %>"><a href="#tab_<%= key %>" id="<%= key %>" data-toggle="tab"><%= key %></a></li>
46
+ <% end %>
47
+ </ul>
48
+ <div class="tab-content">
49
+ <% data.keys.each_with_index do |key, index| %>
50
+ <% first = index == 0 %>
51
+ <div class="tab-pane <%= 'active' if first %>" id="tab_<%= key %>">
52
+ <canvas id="chart_<%= key %>" width="1200" height="400"></canvas>
53
+ </div>
54
+ <% end %>
55
+ </div>
56
+ </div>
57
+
58
+
59
+
60
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.js"></script>
61
+ <script>window.jQuery || document.write('<script src="js/vendor/jquery-1.9.1.js"><\/script>')</script>
62
+
63
+ <script src="js/vendor/bootstrap.min.js"></script>
64
+ <script src="js/vendor/Chart.js"></script>
65
+ <script src="js/main.js"></script>
66
+ </body>
67
+ </html>
data/lib/profit/client.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Profit
2
2
 
3
- def self.client
4
- @client ||= Client.new
3
+ def self.client(options = {})
4
+ @client ||= Client.new(options)
5
5
  end
6
6
 
7
7
  class Client
@@ -11,37 +11,25 @@ module Profit
11
11
  def initialize(options = {})
12
12
  @ctx = options[:ctx] || ZMQ::Context.new
13
13
  @server_address = options[:server_address] || "tcp://127.0.0.1:5556"
14
- @pending = {}
14
+ @pending = {}
15
15
  end
16
16
 
17
- def start(metric_type)
18
- now = Time.now
19
- start_file = caller[0][/(.+):(.+):/,1]
20
- start_line = caller[0][/(.+):(.+):/,2].to_i + 1
21
-
22
- # TODO: wrap in a Mutex & make the key a combo of metric_type,
23
- # pid, and/or thread object_id to make this thread safe and
24
- # thread-robust.
25
- @pending[key_for(metric_type)] = { start_file: start_file,
26
- start_line: start_line,
27
- start_time: now }
17
+ def start(metric_key)
18
+ pending_key = pending_key_for(Key.new(metric_key).to_s)
19
+ metric = Metric.new(metric_key: metric_key,
20
+ start_line: caller[0],
21
+ start_time: Time.now)
22
+
23
+ pending[pending_key] = metric
28
24
  end
29
25
 
30
- def stop(metric_type)
31
- now = Time.now
32
- metric = @pending.delete key_for(metric_type)
33
- recorded_time = now - metric[:start_time]
34
- start_time = metric[:start_time].to_i
35
- stop_file = caller[0][/(.+):(.+):/,1]
36
- stop_line = caller[0][/(.+):(.+):/,2].to_i - 1
37
-
38
- socket.send({ metric_type: metric_type,
39
- recorded_time: recorded_time,
40
- start_time: start_time,
41
- start_file: metric[:start_file],
42
- start_line: metric[:start_line],
43
- stop_file: stop_file,
44
- stop_line: stop_line }.to_json)
26
+ def stop(metric_key)
27
+ pending_key = pending_key_for(Key.new(metric_key).to_s)
28
+ metric = pending.delete pending_key
29
+ metric.recorded_time = Time.now - metric.start_time
30
+ metric.stop_line = caller[0]
31
+
32
+ socket.send(metric.to_json)
45
33
  end
46
34
 
47
35
  def socket
@@ -50,7 +38,7 @@ module Profit
50
38
 
51
39
  private
52
40
 
53
- def key_for(metric_type)
41
+ def pending_key_for(metric_type)
54
42
  "#{metric_type}:#{Thread.current.object_id}"
55
43
  end
56
44
  end
data/lib/profit/key.rb ADDED
@@ -0,0 +1,32 @@
1
+ module Profit
2
+
3
+ class Key
4
+
5
+ class << self
6
+
7
+ def all
8
+ redis.smembers("profit:keys").map { |key| new(key.split(":").last) }
9
+ end
10
+
11
+ def redis
12
+ Profit.redis
13
+ end
14
+ end
15
+
16
+ def initialize(key)
17
+ @key = key
18
+ end
19
+
20
+ def to_s
21
+ @key
22
+ end
23
+
24
+ def metrics
25
+ self.class.redis.lrange(full, 0, -1).map {|key_data| Metric.new(key_data)}.reverse
26
+ end
27
+
28
+ def full
29
+ "profit:metric:#{@key}"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ module Profit
2
+
3
+ class Metric
4
+
5
+ attr_accessor :data,
6
+ :metric_key,
7
+ :recorded_time,
8
+ :start_line,
9
+ :start_time,
10
+ :stop_line
11
+
12
+ def initialize(metric)
13
+ @data = HashWithIndifferentAccess.new(metric.is_a?(String) ? JSON.parse(metric) : metric)
14
+ @metric_key,
15
+ @recorded_time,
16
+ @start_line,
17
+ @start_time,
18
+ @stop_line = data.values_at(:metric_key,
19
+ :recorded_time,
20
+ :start_line,
21
+ :start_time,
22
+ :stop_line)
23
+ end
24
+
25
+ def to_json
26
+ { metric_key: metric_key,
27
+ recorded_time: recorded_time,
28
+ start_line: start_line,
29
+ start_time: start_time.to_i,
30
+ stop_line: stop_line }.to_json
31
+ end
32
+
33
+ def point
34
+ { recorded_time: recorded_time, start_time: start_time }
35
+ end
36
+
37
+ def display_start_time
38
+ DateTime.strptime(start_time.to_s,'%s').strftime("%H:%M:%S")
39
+ end
40
+ end
41
+ end
data/lib/profit/server.rb CHANGED
@@ -4,6 +4,8 @@ module Profit
4
4
 
5
5
  attr_reader :ctx
6
6
 
7
+ METRIC_LIMIT_PER_KEY = 100
8
+
7
9
  def initialize(options = {})
8
10
  @options = {}
9
11
  @options[:redis_address] = options[:redis_address] || "127.0.0.1"
@@ -36,13 +38,21 @@ module Profit
36
38
  # take a worker from the pool to save the metric to Redis
37
39
  redis_pool.perform do |conn|
38
40
 
39
- message_hash = JSON.parse(message)
40
- metric_type = message_hash.delete("metric_type")
41
+ message_hash = JSON.parse(message)
42
+ metric_key = ["profit", "metric", message_hash.delete("metric_key")].join(":")
43
+
44
+ add_key_response = conn.sadd("profit:keys", metric_key)
45
+ add_key_response.callback { |resp| logger.debug "adding metric key callback: #{resp}" }
46
+ add_key_response.errback { |resp| logger.error "adding metric key error: #{resp}"}
47
+
48
+ push_metric_response = conn.lpush metric_key, message_hash.to_json
49
+ push_metric_response.callback { |resp| logger.debug "push metric callback: #{resp}" }
50
+ push_metric_response.errback { |resp| logger.error "push metric error: #{resp}"}
41
51
 
42
- response = conn.rpush "profit:metric:#{metric_type}", message_hash.to_json
43
- response.callback { |resp| logger.debug "callback: #{resp}"}
44
- response.errback { |resp| logger.error "error: #{resp}"}
45
- response
52
+ trim_list_response = conn.ltrim(metric_key, 0, METRIC_LIMIT_PER_KEY - 1)
53
+ trim_list_response.callback { |resp| logger.debug "trim list callback: #{resp}" }
54
+ trim_list_response.errback { |resp| logger.error "trim list error: #{resp}"}
55
+ trim_list_response
46
56
  end
47
57
  end
48
58
  end
@@ -1,3 +1,3 @@
1
1
  module Profit
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6"
3
3
  end
@@ -9,6 +9,7 @@ describe Profit::Client do
9
9
 
10
10
  after do
11
11
  redis.del("profit:metric:some_foo_measurement")
12
+ redis.del("profit:keys")
12
13
  end
13
14
 
14
15
  it "sends the amount of time it takes to run some code" do
@@ -42,6 +43,13 @@ describe Profit::Client do
42
43
  expect(metric['start_time']).to be_within(1).of(now.to_i)
43
44
  end
44
45
 
46
+ describe "Profit.client" do
47
+
48
+ it "creates a single client instance" do
49
+ expect(Profit.client(ctx: server.ctx)).to be_a(Profit::Client)
50
+ end
51
+ end
52
+
45
53
  describe "#ctx" do
46
54
 
47
55
  it "is a ZMQ context" do
@@ -78,7 +86,7 @@ describe Profit::Client do
78
86
 
79
87
  key = client.pending.keys.grep(/some_foo_measurement/).first
80
88
  pending_metric = client.pending[key]
81
- expect(pending_metric[:start_time].to_i).to be_within(1).of(now.to_i)
89
+ expect(pending_metric.start_time.to_i).to be_within(1).of(now.to_i)
82
90
  end
83
91
 
84
92
  it "records where the execution starts" do
@@ -86,12 +94,10 @@ describe Profit::Client do
86
94
 
87
95
  client.start("some_foo_measurement")
88
96
  start_line = __LINE__
89
- start_file = __FILE__
90
97
 
91
98
  key = client.pending.keys.grep(/some_foo_measurement/).first
92
99
  pending_metric = client.pending[key]
93
- expect(pending_metric[:start_file]).to eq start_file
94
- expect(pending_metric[:start_line]).to eq start_line
100
+ expect(pending_metric.start_line).to match(%r{#{__FILE__}:#{start_line - 1}})
95
101
  end
96
102
  end
97
103
 
@@ -125,15 +131,14 @@ describe Profit::Client do
125
131
  expect(first_measurement_list.count).to eq 2
126
132
  expect(second_measurement_list.count).to eq 1
127
133
 
128
- expect(JSON.parse(first_measurement_list[0])['recorded_time']).to be_within(0.1).of(1.3)
129
- expect(JSON.parse(first_measurement_list[1])['recorded_time']).to be_within(0.1).of(0.2)
134
+ expect(JSON.parse(first_measurement_list[0])['recorded_time']).to be_within(0.1).of(0.2)
135
+ expect(JSON.parse(first_measurement_list[1])['recorded_time']).to be_within(0.1).of(1.3)
130
136
  expect(JSON.parse(second_measurement_list[0])['recorded_time']).to be_within(0.1).of(1.2)
131
137
  end
132
138
 
133
139
  it "records where the execution stops" do
134
140
  client.start("m_1")
135
141
  sleep 0.1
136
- stop_file = __FILE__
137
142
  stop_line = __LINE__
138
143
  client.stop("m_1")
139
144
 
@@ -141,8 +146,7 @@ describe Profit::Client do
141
146
 
142
147
  measurements = redis.lrange("profit:metric:m_1", 0, -1)
143
148
  metric = JSON.parse(measurements[0])
144
- expect(metric['stop_file']).to eq stop_file
145
- expect(metric['stop_line']).to eq stop_line
149
+ expect(metric['stop_line']).to match(%r{#{__FILE__}:#{stop_line + 1}})
146
150
  end
147
151
  end
148
152
  end
@@ -0,0 +1,56 @@
1
+ module Profit
2
+
3
+ describe Profit::Key do
4
+
5
+ let!(:redis) { Redis.new(host: "127.0.0.1", port: 6379) }
6
+
7
+ after do
8
+ redis.del("profit:keys")
9
+ end
10
+
11
+ describe ".all" do
12
+
13
+ it "retrieves keys from redis" do
14
+ redis.sadd("profit:keys", "profit:metric:some_great_key")
15
+
16
+ expect(Key.all.map(&:to_s)).to eq(%w{ some_great_key })
17
+ end
18
+ end
19
+
20
+ describe "#to_s" do
21
+
22
+ it "gets back what you put in" do
23
+ expect(Key.new("some_great_key").to_s).to eq "some_great_key"
24
+ end
25
+ end
26
+
27
+ describe "#metrics" do
28
+
29
+ after do
30
+ redis.del("profit:keys:some_metric")
31
+ end
32
+
33
+ it "returns the metrics associated with a key" do
34
+ test_metric = { recorded_time: 0.402520,
35
+ start_time: 1371520170,
36
+ start_line: "/Users/me/foo.rb:20",
37
+ stop_line: "/Users/me/foo.rb:22" }
38
+ expect(DateTime.strptime("1371520170",'%s').strftime("%H:%M:%S")).to eq "01:49:30"
39
+
40
+ redis.rpush("profit:metric:some_metric", test_metric.to_json)
41
+
42
+ key = Key.new("some_metric")
43
+ metric = key.metrics.first
44
+ expect(metric.recorded_time).to eq 0.402520
45
+ expect(metric.start_time).to eq 1371520170
46
+ end
47
+ end
48
+
49
+ describe "#full" do
50
+
51
+ it "returns the full redis key" do
52
+ expect(Key.new("some_foobar").full).to eq "profit:metric:some_foobar"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ module Profit
4
+
5
+ describe Profit::Metric do
6
+
7
+ let(:test_data) do
8
+ { metric_key: 'some_metric',
9
+ recorded_time: 0.10051,
10
+ start_line: "/Users/me/foo.rb:20",
11
+ start_time: 1371420170,
12
+ stop_line: "/Users/me/foo.rb:22" }
13
+ end
14
+
15
+ describe "#new" do
16
+
17
+ it "accepts a json string or a hash to initialize" do
18
+ with_hash = Metric.new(test_data)
19
+ with_json = Metric.new(test_data.to_json)
20
+
21
+ expect(with_hash).to be_a(Metric)
22
+ expect(with_json).to be_a(Metric)
23
+ end
24
+ end
25
+
26
+ describe "#data" do
27
+
28
+ it "returns the hash with which it was initiliazed" do
29
+ with_hash = Metric.new(test_data)
30
+ with_json = Metric.new(test_data.to_json)
31
+
32
+ expect(with_hash.data).to be_a(ActiveSupport::HashWithIndifferentAccess)
33
+ expect(with_json.data).to be_a(ActiveSupport::HashWithIndifferentAccess)
34
+
35
+ expect(with_hash.data).to eq with_json.data
36
+ end
37
+ end
38
+
39
+ describe "#display_start_time" do
40
+
41
+ it "gives the time as %H:%M:%S" do
42
+ metric = Metric.new(test_data.to_json)
43
+ expect(metric.display_start_time).to eq "22:02:50"
44
+ end
45
+ end
46
+
47
+ describe "#start_time" do
48
+
49
+ it "gives the unix time the metric started" do
50
+ metric = Metric.new(test_data.to_json)
51
+ expect(metric.start_time).to eq 1371420170
52
+ end
53
+ end
54
+
55
+ describe "#recorded_time" do
56
+
57
+ it "gives the difference in time as a float" do
58
+ metric = Metric.new(test_data.to_json)
59
+ expect(metric.recorded_time).to eq 0.10051
60
+ end
61
+ end
62
+
63
+ describe "#point" do
64
+
65
+ it "returns the recorded_time and start_time" do
66
+ metric = Metric.new(test_data.to_json)
67
+ expect(metric.point).to eq({ recorded_time: metric.recorded_time,
68
+ start_time: metric.start_time })
69
+ end
70
+ end
71
+
72
+ describe "#start_line" do
73
+
74
+ it "returns a string" do
75
+ metric = Metric.new(test_data)
76
+ expect(metric.start_line).to eq "/Users/me/foo.rb:20"
77
+ end
78
+ end
79
+
80
+ describe "#stop_line" do
81
+
82
+ it "returns a string" do
83
+ metric = Metric.new(test_data)
84
+ expect(metric.stop_line).to eq "/Users/me/foo.rb:22"
85
+ end
86
+ end
87
+
88
+ describe "#metric_key" do
89
+
90
+ it "returns the key name" do
91
+ metric = Metric.new(test_data)
92
+ expect(metric.metric_key).to eq "some_metric"
93
+ end
94
+ end
95
+
96
+ describe "#to_json" do
97
+
98
+ it "converts to a json string" do
99
+ metric = Metric.new(test_data)
100
+ expect(metric.to_json).to eq test_data.to_json
101
+ end
102
+ end
103
+ end
104
+ end