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.
- checksums.yaml +7 -0
- data/README.md +1 -1
- data/bin/profit_app +24 -0
- data/lib/profit.rb +12 -0
- data/lib/profit/app/chart_app.rb +25 -0
- data/lib/profit/app/public/css/bootstrap-responsive.css +1109 -0
- data/lib/profit/app/public/css/bootstrap-responsive.min.css +9 -0
- data/lib/profit/app/public/css/bootstrap.css +6158 -0
- data/lib/profit/app/public/css/bootstrap.min.css +9 -0
- data/lib/profit/app/public/css/main.css +22 -0
- data/lib/profit/app/public/img/glyphicons-halflings-white.png +0 -0
- data/lib/profit/app/public/img/glyphicons-halflings.png +0 -0
- data/lib/profit/app/public/js/main.js +22 -0
- data/lib/profit/app/public/js/vendor/Chart.js +1426 -0
- data/lib/profit/app/public/js/vendor/bootstrap.js +2268 -0
- data/lib/profit/app/public/js/vendor/bootstrap.min.js +6 -0
- data/lib/profit/app/public/js/vendor/html5shiv.js +5 -0
- data/lib/profit/app/public/js/vendor/jquery-1.9.1.js +9597 -0
- data/lib/profit/app/views/index.erb +67 -0
- data/lib/profit/client.rb +18 -30
- data/lib/profit/key.rb +32 -0
- data/lib/profit/metric.rb +41 -0
- data/lib/profit/server.rb +16 -6
- data/lib/profit/version.rb +1 -1
- data/spec/lib/client_spec.rb +13 -9
- data/spec/lib/key_spec.rb +56 -0
- data/spec/lib/metric_spec.rb +104 -0
- data/spec/lib/server_spec.rb +58 -1
- data/spec/profit_app/chart_app_spec.rb +28 -0
- data/spec/spec_helper.rb +10 -0
- metadata +130 -43
@@ -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(
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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(
|
31
|
-
|
32
|
-
metric
|
33
|
-
recorded_time = now - metric
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
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
|
40
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
data/lib/profit/version.rb
CHANGED
data/spec/lib/client_spec.rb
CHANGED
@@ -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
|
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
|
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(
|
129
|
-
expect(JSON.parse(first_measurement_list[1])['recorded_time']).to be_within(0.1).of(
|
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['
|
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
|