batsd-dash 0.2.1 → 0.3.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/Gemfile +7 -0
- data/Guardfile +10 -0
- data/README.md +40 -43
- data/batsd-dash.gemspec +1 -1
- data/lib/batsd-dash/graph.rb +31 -22
- data/lib/batsd-dash/params.rb +11 -5
- data/lib/batsd-dash/sass/public.scss +99 -6
- data/lib/batsd-dash/version.rb +1 -1
- data/lib/batsd-dash.rb +24 -21
- data/lib/public/css/d3.css +522 -0
- data/lib/public/css/public.css +69 -4
- data/lib/public/js/d3.js +4 -0
- data/lib/public/js/dash.js +81 -60
- data/lib/public/js/nv.d3.js +4 -0
- data/lib/views/layout.haml +9 -6
- data/lib/views/view.haml +8 -0
- data/test/test_dash.rb +99 -0
- metadata +28 -24
- data/test/test_params_helper.rb +0 -86
data/Gemfile
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'minitest' do
|
5
|
+
# with Minitest::Unit
|
6
|
+
watch(%r|^test/(.*)\/?test_(.*)\.rb|)
|
7
|
+
watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "test" }
|
8
|
+
watch(%r|^test/test_helper\.rb|) { "test" }
|
9
|
+
end
|
10
|
+
|
data/README.md
CHANGED
@@ -17,69 +17,62 @@ Here is a sample rackup file (`config.ru`):
|
|
17
17
|
|
18
18
|
require 'batsd-dash'
|
19
19
|
|
20
|
-
# set batsd server setting
|
20
|
+
# set batsd server setting
|
21
|
+
BatsdDash::ConnectionPool.settings = { host:'localhost', port: 8127, pool_size: 8 }
|
21
22
|
|
22
|
-
# run the app
|
23
|
+
# run the app
|
24
|
+
run BatsdDash::App
|
23
25
|
|
24
|
-
Rack is very powerful. You can password protect your batsd-dash instance
|
25
|
-
using `Rack::Auth::Basic` or `Rack::Auth::Digest::MD5`.
|
26
|
+
Rack is very powerful. You can password protect your batsd-dash instance
|
27
|
+
by using `Rack::Auth::Basic` or `Rack::Auth::Digest::MD5`.
|
26
28
|
|
27
|
-
##
|
29
|
+
## Viewing Graphs
|
28
30
|
|
29
|
-
|
31
|
+
Graphs are rendered using [nv.d3](http://nvd3.com/), a powerful graph
|
32
|
+
and visualization library.
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
request would access data for counter based metric:
|
35
|
-
|
36
|
-
/counters?metric=a.b
|
37
|
-
|
38
|
-
It's possible to access data for more than one metric within a single request.
|
39
|
-
For example, the following request route will return data for both the `a.b`
|
40
|
-
metric and the `c.d` metric:
|
34
|
+
Since rendering is all done on the client, we make use of hash based
|
35
|
+
navigation in order to reduce the amount of requests while still
|
36
|
+
maintaining 'linkability'.
|
41
37
|
|
42
|
-
|
38
|
+
For example, to view a graph for the counter `a.b` statistic, you would need
|
39
|
+
to make the following request from your browser:
|
43
40
|
|
44
|
-
|
45
|
-
accessing different ranges of data.
|
41
|
+
/graph#counters=a.b
|
46
42
|
|
47
|
-
|
48
|
-
|
43
|
+
The graph view will provide you with a date time picker to make selecting
|
44
|
+
different time ranges easier. Graphs are updated when you press the
|
45
|
+
'View' button or when the URL is updated.
|
49
46
|
|
50
|
-
|
47
|
+
It's possible to view more than one metric at the same time. To do this,
|
48
|
+
visit the following route from your browser:
|
51
49
|
|
52
|
-
|
53
|
-
element to create graphs. Since rendering is all done on the client, we make use
|
54
|
-
of hash based navigation in order to reduce the amount of requests and while
|
55
|
-
maintaining 'linkability'.
|
50
|
+
/graph#counters=a.b,c.d
|
56
51
|
|
57
|
-
|
58
|
-
request from your browser:
|
52
|
+
You can also view different datatypes at the same time:
|
59
53
|
|
60
|
-
/counters
|
54
|
+
/graph#counters=a.b&timers=x.y
|
61
55
|
|
62
|
-
|
63
|
-
|
64
|
-
the 'View' button.
|
56
|
+
__NOTE__: As of now, a single y-axis is used when datatypes are mixed.
|
57
|
+
Soon, we will add support for multiple axis when viewing mixed types.
|
65
58
|
|
66
|
-
|
67
|
-
time. To do this, visit the following route from your browser:
|
59
|
+
## Data API
|
68
60
|
|
69
|
-
|
61
|
+
The application provides a simple JSON-based API for accessing data from
|
62
|
+
the batds data server. The data API accepts similar parameters as the
|
63
|
+
graph view but uses traditional query strings instead:
|
70
64
|
|
71
|
-
|
72
|
-
strange looking. This is something we will improve upon. Additionally, we also
|
73
|
-
plan to add some sort of tree-based widget for selecting different metrics to
|
74
|
-
view.
|
65
|
+
/data?counters[]=a.b&counters[]=c.d&timers[]=x.y
|
75
66
|
|
76
|
-
|
67
|
+
The data API also accepts a `start` and `stop` unix timestamp parameter
|
68
|
+
for accessing different ranges of data. Note that, the data API will
|
69
|
+
only respond with JSON if the `Accept` header to set to `application/json`!
|
77
70
|
|
78
|
-
|
71
|
+
## Graph and Render Options
|
79
72
|
|
80
|
-
|
73
|
+
1. Zerofill:
|
74
|
+
__TODO__ Add details about zerofill
|
81
75
|
|
82
|
-
_TODO_ Setup client to accept pass along no-zerofill options.
|
83
76
|
|
84
77
|
## Development
|
85
78
|
|
@@ -92,3 +85,7 @@ files, ensure you recompile the CSS. This is done by running:
|
|
92
85
|
|
93
86
|
Additionally, it is highly recommended you use thin for development since this
|
94
87
|
app uses EventMachine.
|
88
|
+
|
89
|
+
## About
|
90
|
+
|
91
|
+
This is project is maintained and developed by the people behind [BreakBase](http://breakbase.com) ([@mikeycgto](https://twitter.com/mikeycgto) and [@btoconnor](https://twitter.com/btoconnor))
|
data/batsd-dash.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.homepage = "https://github.com/mikeycgto/batsd-dash"
|
13
13
|
|
14
14
|
s.summary = %q{batsd-dash}
|
15
|
-
s.description = %q{batsd-dash - graphs and stuff from
|
15
|
+
s.description = %q{batsd-dash - graphs and stuff from batsd. yay!}
|
16
16
|
|
17
17
|
s.rubyforge_project = "batsd-dash"
|
18
18
|
|
data/lib/batsd-dash/graph.rb
CHANGED
@@ -2,42 +2,51 @@
|
|
2
2
|
module BatsdDash
|
3
3
|
module GraphHelper
|
4
4
|
##
|
5
|
-
# This method works directly against values
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# apply zerofill
|
12
|
-
zero_fill!(pts, opts[:range], opts[:interval]) unless pts.empty? || !opts[:zero_fill]
|
13
|
-
end
|
14
|
-
end
|
5
|
+
# This method works directly against values. It will tranform all
|
6
|
+
# datapoint to an array where the first element is a milisecond
|
7
|
+
# timestamp and the second is a float value data point.
|
8
|
+
def values_for_graph(values, opts = {})
|
9
|
+
return values if values.empty?
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
def zero_fill!(values, range, step)
|
20
|
-
return values if step.zero?
|
11
|
+
values.tap do |pts|
|
12
|
+
step = opts[:interval] * 1000
|
13
|
+
range = opts[:range]
|
21
14
|
|
22
|
-
|
23
|
-
|
15
|
+
# transform the first point
|
16
|
+
transform_point_at!(0, values)
|
24
17
|
|
25
|
-
values.tap do |data|
|
26
18
|
# start from the first timestamp
|
27
|
-
time =
|
19
|
+
time = values.first.first + step
|
28
20
|
index = 0
|
29
21
|
|
30
|
-
|
22
|
+
# loop through values to transform and zerofill
|
23
|
+
while index < values.size - 1
|
24
|
+
obj = transform_point_at!(index += 1, values)
|
25
|
+
|
31
26
|
if obj.first <= time
|
32
27
|
time += step
|
33
28
|
next
|
34
29
|
end
|
35
30
|
|
36
|
-
|
37
|
-
|
31
|
+
# need to insert zerofilled point (if zerofill is enabled)
|
32
|
+
values.insert(index, [time, 0]) if opts[:zero_fill]
|
38
33
|
time += step
|
39
34
|
end
|
40
35
|
end
|
41
36
|
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Transform a point at a given index
|
40
|
+
def transform_point_at!(index, values)
|
41
|
+
data_pt = values[index]
|
42
|
+
|
43
|
+
# we've already transformed this index (must be zerofill)
|
44
|
+
return data_pt unless Hash === data_pt
|
45
|
+
|
46
|
+
pt_time = data_pt['timestamp'].to_i * 1000
|
47
|
+
pt_value = data_pt['value'].to_f
|
48
|
+
|
49
|
+
values[index] = [pt_time, pt_value]
|
50
|
+
end
|
42
51
|
end
|
43
52
|
end
|
data/lib/batsd-dash/params.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
# helpers for processing params and validating input
|
2
2
|
module BatsdDash
|
3
3
|
module ParamsHelper
|
4
|
-
def
|
5
|
-
|
6
|
-
|
4
|
+
def parse_statistics
|
5
|
+
Hash.new { |hash,key| hash[key] = [] }.tap do |stats|
|
6
|
+
%w[ counters gauges timers ].each do |datatype|
|
7
|
+
list = params[datatype]
|
7
8
|
|
8
|
-
|
9
|
+
list = [list] unless Array === list
|
10
|
+
list.reject! { |m| m.nil? || m.empty? }
|
11
|
+
|
12
|
+
stats[datatype] = list unless list.empty?
|
13
|
+
end
|
14
|
+
end
|
9
15
|
end
|
10
16
|
|
11
17
|
def parse_time_range
|
@@ -16,7 +22,7 @@ module BatsdDash
|
|
16
22
|
|
17
23
|
# 1 hr range
|
18
24
|
# TODO make this setting?
|
19
|
-
[ now -
|
25
|
+
[ now - 1800, now ]
|
20
26
|
|
21
27
|
else
|
22
28
|
[start.to_i, stop.to_i].tap do |range|
|
@@ -1,15 +1,31 @@
|
|
1
|
-
@import "compass/utilities/general/clearfix";
|
2
1
|
@import "compass/css3";
|
2
|
+
|
3
|
+
@import "compass/utilities/general/clearfix";
|
3
4
|
@import "compass/reset";
|
4
5
|
|
6
|
+
$green: #0F0;
|
7
|
+
$graphHeight:400px;
|
8
|
+
$graphWidth:880px;
|
9
|
+
|
10
|
+
@mixin absolute-position($height, $width){
|
11
|
+
height:$height;
|
12
|
+
width:$width;
|
13
|
+
|
14
|
+
position:absolute;
|
15
|
+
top: ($graphHeight / 2) - ($height / 2);
|
16
|
+
left: ($graphWidth / 2) - ($width / 2);
|
17
|
+
}
|
18
|
+
|
19
|
+
|
5
20
|
html, body { height:100%; min-height:100%; background-color:#222; }
|
6
|
-
|
21
|
+
|
22
|
+
h1, h2, strong, em { color:$green; font-family:"Courier New",Courier,monospace; }
|
7
23
|
a { text-decoration:none; color:inherit; }
|
8
24
|
|
9
25
|
.wrap { width:1000px; margin:0 auto; }
|
10
26
|
|
11
27
|
#header {
|
12
|
-
height:60px; border:1px solid
|
28
|
+
height:60px; border:1px solid $green; border-width:0 0 1px 0; margin:0 0 20px;
|
13
29
|
|
14
30
|
background-color: #000000;
|
15
31
|
@include filter-gradient(#000000, #222, vertical);
|
@@ -28,7 +44,6 @@ a { text-decoration:none; color:inherit; }
|
|
28
44
|
}
|
29
45
|
}
|
30
46
|
|
31
|
-
|
32
47
|
#content {
|
33
48
|
color:#FFF; width:1000px; margin:0 auto; font-size:18px;
|
34
49
|
|
@@ -40,9 +55,23 @@ a { text-decoration:none; color:inherit; }
|
|
40
55
|
}
|
41
56
|
|
42
57
|
.graph {
|
43
|
-
|
58
|
+
height:$graphHeight;
|
59
|
+
width:$graphWidth;
|
60
|
+
|
61
|
+
position:relative;
|
62
|
+
margin:0 auto;
|
63
|
+
|
64
|
+
em {
|
65
|
+
@include absolute-position(30px, 300px);
|
44
66
|
|
45
|
-
|
67
|
+
padding:2px 0;
|
68
|
+
font-size:26px;
|
69
|
+
text-align:center;
|
70
|
+
|
71
|
+
-webkit-animation-name:blink;
|
72
|
+
-webkit-animation-duration:1s;
|
73
|
+
-webkit-animation-iteration-count:infinite;
|
74
|
+
}
|
46
75
|
}
|
47
76
|
|
48
77
|
.inputs {
|
@@ -53,4 +82,68 @@ a { text-decoration:none; color:inherit; }
|
|
53
82
|
}
|
54
83
|
}
|
55
84
|
|
85
|
+
#loading {
|
86
|
+
@include absolute-position(100px, 280px);
|
87
|
+
|
88
|
+
display:none;
|
89
|
+
|
90
|
+
strong {
|
91
|
+
float:right;
|
92
|
+
font-size:26px;
|
93
|
+
margin:36px 0 0;
|
94
|
+
}
|
95
|
+
|
96
|
+
#spinner {
|
97
|
+
position:relative;
|
98
|
+
float:left;
|
99
|
+
width:100px;
|
100
|
+
height:100px;
|
101
|
+
|
102
|
+
-webkit-animation-name: rotateSpinner;
|
103
|
+
-webkit-animation-duration:2s;
|
104
|
+
-webkit-animation-iteration-count:infinite;
|
105
|
+
-webkit-animation-timing-function:linear;
|
106
|
+
|
107
|
+
div {
|
108
|
+
width:10px;
|
109
|
+
height:30px;
|
110
|
+
background:$green;
|
111
|
+
|
112
|
+
position: absolute;
|
113
|
+
top:35px;
|
114
|
+
left:45px;
|
115
|
+
}
|
116
|
+
|
117
|
+
$i:1;
|
118
|
+
$rotate:0deg;
|
119
|
+
$opacity:0.12;
|
120
|
+
|
121
|
+
@while ($i <= 8) {
|
122
|
+
.bar#{$i} {
|
123
|
+
@include simple-transform(1, $rotate, 0px, -40px);
|
124
|
+
|
125
|
+
opacity:$opacity;
|
126
|
+
}
|
127
|
+
|
128
|
+
@if ($i % 2 == 0){
|
129
|
+
$opacity:$opacity + 0.13;
|
130
|
+
} @else {
|
131
|
+
$opacity:$opacity + 0.12;
|
132
|
+
}
|
133
|
+
|
134
|
+
$rotate: $rotate + 45deg;
|
135
|
+
$i: $i + 1;
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
@-webkit-keyframes rotateSpinner {
|
141
|
+
from { -webkit-transform:scale(0.5) rotate(0deg); }
|
142
|
+
to { -webkit-transform:scale(0.5) rotate(360deg); }
|
143
|
+
}
|
56
144
|
|
145
|
+
@-webkit-keyframes blink {
|
146
|
+
0% { opacity:1.0; }
|
147
|
+
50% { opacity:0.25; }
|
148
|
+
100% { opacity:1.0; }
|
149
|
+
}
|
data/lib/batsd-dash/version.rb
CHANGED
data/lib/batsd-dash.rb
CHANGED
@@ -42,41 +42,44 @@ module BatsdDash
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
45
|
+
# this route renders the template (with codes for the graph)
|
46
|
+
get "/graph", :provides => :html do
|
47
|
+
haml :view
|
48
|
+
end
|
50
49
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
50
|
+
# actual data API route
|
51
|
+
get "/data", :provides => :json do
|
52
|
+
statistics = parse_statistics
|
53
|
+
range = parse_time_range
|
55
54
|
|
56
|
-
|
57
|
-
|
55
|
+
return render_error('Invalid time range') unless range
|
56
|
+
return render_error('Invalid metrics') if statistics.empty?
|
58
57
|
|
59
|
-
|
60
|
-
|
58
|
+
results = []
|
59
|
+
options = {
|
60
|
+
range: range.dup.map { |n| n * 1000 },
|
61
|
+
zero_fill: !params[:no_zero_fill]
|
62
|
+
}
|
61
63
|
|
64
|
+
statistics.each do |datatype, metrics|
|
62
65
|
metrics.each do |metric|
|
63
66
|
statistic = "#{datatype}:#{metric}"
|
64
67
|
deferrable = connection_pool.async_values(statistic, range)
|
65
68
|
|
66
69
|
deferrable.errback { |e| return render_error(e.message) }
|
67
70
|
deferrable.callback do |json|
|
68
|
-
|
71
|
+
options[:interval] ||= json['interval']
|
72
|
+
|
73
|
+
points = json[statistic] || []
|
74
|
+
values = values_for_graph(points, options)
|
69
75
|
|
70
|
-
|
71
|
-
collect_opts.merge!(interval: json['interval'] || 0) unless collect_opts.has_key?(:interval)
|
72
|
-
# process values for graphing and add to results
|
73
|
-
results[:metrics] << { label: metric, data: collect_for_graph(values, collect_opts) }
|
76
|
+
results << { key: metric, type: datatype[0..-2], values: values }
|
74
77
|
end
|
75
78
|
end
|
76
|
-
|
77
|
-
cache_control :no_cache, :no_store
|
78
|
-
render_json results
|
79
79
|
end
|
80
|
+
|
81
|
+
cache_control :no_cache, :no_store
|
82
|
+
render_json range: options[:range], interval: options[:interval], results: results
|
80
83
|
end
|
81
84
|
end
|
82
85
|
end
|