profit 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|