profit 0.1.5 → 0.1.6

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.
@@ -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