growthforecast 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +13 -0
- data/README.md +194 -0
- data/Rakefile +2 -0
- data/example/test1.rb +94 -0
- data/growthforecast.gemspec +17 -0
- data/lib/growthforecast/complex.rb +75 -0
- data/lib/growthforecast/graph.rb +70 -0
- data/lib/growthforecast/path.rb +58 -0
- data/lib/growthforecast/version.rb +3 -0
- data/lib/growthforecast.rb +235 -0
- metadata +58 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2013- TAGOMORI Satoshi
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
# GrowthForecast
|
2
|
+
|
3
|
+
Client library to operate GrowthForecast.
|
4
|
+
|
5
|
+
* http://kazeburo.github.com/GrowthForecast/ (Japanese)
|
6
|
+
* https://github.com/kazeburo/growthforecast
|
7
|
+
|
8
|
+
Update graph value, or create/edit/delete basic graphs and complex graphs.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'growthforecast'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install growthforecast
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Update graph's value:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require 'growthforecast'
|
30
|
+
gf = GrowthForecast.new('your.gf.host.name', 5125)
|
31
|
+
|
32
|
+
gf.post('servicename', 'sectionname', 'graphname', 50)
|
33
|
+
#### or post with graph mode and color
|
34
|
+
# gf.post('servicename', 'sectionname', 'graphname', 50, 'gauge', '#ff00ff')
|
35
|
+
```
|
36
|
+
|
37
|
+
Get graph list and full descripted object:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
glist = gf.graphs() #=> [] or array of GrowthForecast::Path
|
41
|
+
|
42
|
+
glist[0].id
|
43
|
+
glist[0].service_name
|
44
|
+
glist[0].section_name
|
45
|
+
glist[0].graph_name
|
46
|
+
glist[0].complex? #=> false
|
47
|
+
|
48
|
+
graph = gf.graph(glist[0].id) #=> instance of GrowthForecast::Graph
|
49
|
+
|
50
|
+
graph.id
|
51
|
+
graph.description
|
52
|
+
graph.color
|
53
|
+
graph.type
|
54
|
+
graph.number
|
55
|
+
graph.created_at #=> instance of Time
|
56
|
+
|
57
|
+
### complex
|
58
|
+
clist = gf.complexes() #=> [] or array of GrowthForecast::Path
|
59
|
+
|
60
|
+
clist[0].id
|
61
|
+
clist[0].service_name
|
62
|
+
clist[0].section_name
|
63
|
+
clist[0].graph_name
|
64
|
+
clist[0].complex? #=> true
|
65
|
+
|
66
|
+
complex = gf.complex(clist[0].id) #=> instance of GrowthForecast::Complex
|
67
|
+
|
68
|
+
complex.id
|
69
|
+
complex.description
|
70
|
+
complex.data #=> array of GrowthForecast::Complex::Item
|
71
|
+
|
72
|
+
complex.data[1].graph_id #=> id of GrowthForecast::Graph
|
73
|
+
complex.data[1].stack #=> boolean
|
74
|
+
complex.data[1].gmode #=> 'gauge' or 'subtract'
|
75
|
+
complex.data[1].type #=> 'AREA', 'LINE1' or 'LINE2'
|
76
|
+
|
77
|
+
### get data of elements of complex graph
|
78
|
+
|
79
|
+
complex.data.each do |e|
|
80
|
+
graph = gf.graph(e.graph_id) #=> GrowthForecast::Graph
|
81
|
+
# ...
|
82
|
+
end
|
83
|
+
|
84
|
+
### get full list of both of graph and complex
|
85
|
+
|
86
|
+
list = gf.all() #=> array of GrowthForecast::Graph and GrowthForecast::Complex
|
87
|
+
|
88
|
+
tree = gf.tree()
|
89
|
+
tree['service']['section']['name']
|
90
|
+
#=> instance of GrowthForecast::Graph or GrowthForecast::Complex
|
91
|
+
|
92
|
+
### Or more simply, get one by path name
|
93
|
+
### (heavy operation: consider to cache result of gf.all() or gf.tree() to call multiple times)
|
94
|
+
one = gf.by_name('service', 'section', 'name')
|
95
|
+
#=> instance of GrowthForecast::Graph or GrowthForecast::Complex
|
96
|
+
```
|
97
|
+
|
98
|
+
To edit:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
target = gf.graph(glist.first.id)
|
102
|
+
|
103
|
+
target.description = 'OK, we are now testing...'
|
104
|
+
target.color = '#000000'
|
105
|
+
target.type = 'LINE2'
|
106
|
+
|
107
|
+
result = gf.edit(target) #=> boolean: success or not
|
108
|
+
|
109
|
+
raise "Something goes wrong..." unless result
|
110
|
+
|
111
|
+
### invert complex graph element order
|
112
|
+
complex = gf.complex(clist.last.id)
|
113
|
+
|
114
|
+
complex.data.reverse!
|
115
|
+
|
116
|
+
gf.edit(complex)
|
117
|
+
|
118
|
+
### add graph into complex graph's data
|
119
|
+
|
120
|
+
item = GrowthForecast::Complex::Item.new({graph_id: g.id, stack: true, gmode: 'gauge', type: 'AREA'})
|
121
|
+
complex.data.push(item)
|
122
|
+
|
123
|
+
gf.edit(complex)
|
124
|
+
|
125
|
+
### delete graph
|
126
|
+
gf.delete(graph) #=> if false, not deleted correctly...
|
127
|
+
gf.delete(complex)
|
128
|
+
```
|
129
|
+
|
130
|
+
To add graph, `add_graph()` and `add()` are available:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
# add_graph(service, section, graph_name, initial_value=0, color=nil, mode=nil)
|
134
|
+
## color default: nil(random)
|
135
|
+
## mode default: nil('gauge')
|
136
|
+
gf.add_graph('example', 'test', 'graph1')
|
137
|
+
|
138
|
+
# add(spec)
|
139
|
+
## spec: instance of GrowthForecast::Graph
|
140
|
+
spec = GrowthForecast::Graph({service_name: 'example', section_name: 'test', graph_name: 'graph1', color: '#0000ff'})
|
141
|
+
gf.add(spec)
|
142
|
+
```
|
143
|
+
|
144
|
+
As same as graph, to add complex, `add_complex()` and `add()` are available:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
# add_complex(service, section, graph_name, description, sumup, sort, type, gmode, stack, data_graph_ids)
|
148
|
+
## sumup: true or false
|
149
|
+
## sort: 0-19
|
150
|
+
## type,gmode,stack: specify all options of members of data, with same value
|
151
|
+
## type: 'AREA', 'LINE1', 'LINE2'
|
152
|
+
## gmode: 'gauge', 'subtract'
|
153
|
+
## stack: true or false
|
154
|
+
gf.add_complex('example', 'test', 'summary1', 'testing...', true, 0, 'AREA', 'gauge', true, [graph1.id, graph2.id])
|
155
|
+
|
156
|
+
# add(spec)
|
157
|
+
## spec: instance of GrowthForecast::Complex
|
158
|
+
spec = GrowthForecast::Complex({
|
159
|
+
service_name: 'example', section_name: 'test', graph_name: 'summary1',
|
160
|
+
description: 'testing...', sumup: true,
|
161
|
+
data: graph_id_list.map{|id| GrowthForecast::Complex::Item.new({graph_id: id, type: 'AREA', gmode: 'gauge', stack: true}) }
|
162
|
+
})
|
163
|
+
# hmm, i want not to stack last of data, and want to show by bold line.
|
164
|
+
spec.data.last.stack = false
|
165
|
+
spec.data.last.type = 'LINE2'
|
166
|
+
|
167
|
+
gf.add(spec)
|
168
|
+
```
|
169
|
+
|
170
|
+
`add()` accepts graph/complex instance already exists as template:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
# copy template complex graph
|
174
|
+
complex_spec = gf.complex(template.id)
|
175
|
+
|
176
|
+
complex_spec.graph_name = 'copy_of_template_1' # you MUST change one of service/section/graph at least
|
177
|
+
complex_spec.description = '....'
|
178
|
+
|
179
|
+
gf.add(complex_spec) # add() ignores 'id' attribute
|
180
|
+
```
|
181
|
+
|
182
|
+
## TODO
|
183
|
+
|
184
|
+
* validations of specs
|
185
|
+
* diff of 2 graph objects
|
186
|
+
* bin/gfclient
|
187
|
+
* tests
|
188
|
+
|
189
|
+
## Copyright
|
190
|
+
|
191
|
+
* Copyright (c) 2013- TAGOMORI Satoshi (tagomoris)
|
192
|
+
* License
|
193
|
+
* Apache License, Version 2.0
|
194
|
+
* see 'LICENSE'
|
data/Rakefile
ADDED
data/example/test1.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'minitest/unit'
|
4
|
+
include MiniTest::Assertions
|
5
|
+
|
6
|
+
require 'growthforecast'
|
7
|
+
|
8
|
+
gf = GrowthForecast.new
|
9
|
+
|
10
|
+
start_graphs = gf.all().size
|
11
|
+
|
12
|
+
# value update
|
13
|
+
r = gf.post('example', 'test', 'graph2', Process.pid)
|
14
|
+
g1a = gf.graph(r.id)
|
15
|
+
g1b = gf.post('example', 'test', 'graph2', 0, 'count')
|
16
|
+
assert (g1a.number == g1b.number), "if number changed, something goes wrong..."
|
17
|
+
|
18
|
+
# get graph
|
19
|
+
r = gf.graph(99999) # maybe non-existing graph id
|
20
|
+
assert r.nil?, "may be nil"
|
21
|
+
|
22
|
+
# list check
|
23
|
+
glist = gf.graphs()
|
24
|
+
assert (glist.is_a?(Array) and glist.size >= 0), "lists must be not nil"
|
25
|
+
|
26
|
+
# add graph and edit it
|
27
|
+
r = gf.add_graph('example', 'test', 'graphrb' + Process.pid.to_s + 'a', 0, '#ff0000')
|
28
|
+
g2 = gf.by_name('example', 'test', 'graphrb' + Process.pid.to_s + 'a')
|
29
|
+
|
30
|
+
assert (g2.color == '#ff0000'), "added graph color must be #ffff00"
|
31
|
+
assert (g2.type == 'AREA'), "initial type of graph may be AREA"
|
32
|
+
|
33
|
+
g2.type = 'LINE2'
|
34
|
+
|
35
|
+
r = gf.edit(g2)
|
36
|
+
|
37
|
+
g3 = gf.graph(g2.id)
|
38
|
+
assert (g3.type == 'LINE2'), "type not changed correctly #{g3.type}"
|
39
|
+
|
40
|
+
glist2 = gf.graphs()
|
41
|
+
assert (glist.size + 1 == glist2.size), "graphs not added one: #{glist.size} -> #{glist2.size}"
|
42
|
+
|
43
|
+
# add graph by spec
|
44
|
+
spec = GrowthForecast::Graph.new({
|
45
|
+
'service_name' => 'example', 'section_name' => 'test', 'graph_name' => 'graphrb' + Process.pid.to_s + 'b',
|
46
|
+
'color' => '#00FF00', 'mode' => 'derive',
|
47
|
+
})
|
48
|
+
r = gf.add(spec)
|
49
|
+
|
50
|
+
assert r, "add failed with return value #{r}"
|
51
|
+
|
52
|
+
g4 = gf.by_name('example', 'test', 'graphrb' + Process.pid.to_s + 'b')
|
53
|
+
assert (g4.mode == 'derive'), "mode should be derive, but #{g4.mode}"
|
54
|
+
|
55
|
+
# complex
|
56
|
+
r = gf.complex(99999) # may be non-existing id
|
57
|
+
assert r.nil?, "may be nil"
|
58
|
+
|
59
|
+
clist = gf.complexes()
|
60
|
+
assert (clist.is_a?(Array) and clist.size >= 0), "lists must be not nil"
|
61
|
+
|
62
|
+
r = gf.add_complex('example', 'test', 'graphrb' + Process.pid.to_s + 'c', 'testing now', true, 19, 'LINE1', 'gauge', true, [g1b.id, g2.id, g4.id])
|
63
|
+
|
64
|
+
assert r, "add_complex failed with return value #{r}"
|
65
|
+
c1 = gf.by_name('example', 'test', 'graphrb' + Process.pid.to_s + 'c')
|
66
|
+
assert c1.complex?, "c1 is not complex, why?"
|
67
|
+
|
68
|
+
c2spec = gf.complex(c1) #copy of c1 instance
|
69
|
+
c2spec.graph_name = 'graphrb' + Process.pid.to_s + 'd'
|
70
|
+
c2spec.data = [
|
71
|
+
GrowthForecast::Complex::Item.new(graph_id: g2.id),
|
72
|
+
GrowthForecast::Complex::Item.new(graph_id: g4.id),
|
73
|
+
]
|
74
|
+
|
75
|
+
r = gf.add(c2spec)
|
76
|
+
|
77
|
+
assert r, "add failed with return value #{r}"
|
78
|
+
|
79
|
+
c2 = gf.by_name('example', 'test', 'graphrb' + Process.pid.to_s + 'd')
|
80
|
+
c2.sort = 0
|
81
|
+
|
82
|
+
r = gf.edit(c2)
|
83
|
+
|
84
|
+
c2 = gf.complex(c2)
|
85
|
+
assert (c2.sort == 0), "sort value not updated correctly"
|
86
|
+
|
87
|
+
# delete graphs
|
88
|
+
unless gf.delete(c2) && gf.delete(c1) && gf.delete(g4) && gf.delete(g2) && gf.delete(g1b)
|
89
|
+
raise "failed to delete graphs ..."
|
90
|
+
end
|
91
|
+
|
92
|
+
end_graphs = gf.all().size
|
93
|
+
assert (start_graphs == end_graphs), "start graph nums and end graph nums mismatch: #{start_graphs} -> #{end_graphs}"
|
94
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/growthforecast/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["TAGOMORI Satoshi"]
|
6
|
+
gem.email = ["tagomoris@gmail.com"]
|
7
|
+
gem.description = %q{Client library and tool to update values, create/edit/delete graphs of GrowthForecast}
|
8
|
+
gem.summary = %q{A client library for GrowthForecast}
|
9
|
+
gem.homepage = "https://github.com/tagomoris/rb-growthforecast"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "growthforecast"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = GrowthForecast::VERSION
|
17
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
class GrowthForecast::Complex
|
5
|
+
attr_accessor :id, :service_name, :section_name, :graph_name
|
6
|
+
attr_accessor :description, :sort, :sumup
|
7
|
+
attr_accessor :data
|
8
|
+
attr_accessor :number, :data, :created_at, :updated_at
|
9
|
+
|
10
|
+
# TODO strict validations
|
11
|
+
|
12
|
+
def initialize(obj)
|
13
|
+
if obj.is_a?(String)
|
14
|
+
obj = JSON.parse(obj)
|
15
|
+
end
|
16
|
+
obj.keys.each do |k|
|
17
|
+
obj[k.to_sym] = obj.delete(k)
|
18
|
+
end
|
19
|
+
|
20
|
+
if not obj[:complex]
|
21
|
+
raise ArgumentError, "non-complex graph is for GrowthForecast::Complex"
|
22
|
+
end
|
23
|
+
|
24
|
+
@id = obj[:id] ? obj[:id].to_i : nil
|
25
|
+
@service_name = obj[:service_name]
|
26
|
+
@section_name = obj[:section_name]
|
27
|
+
@graph_name = obj[:graph_name]
|
28
|
+
@description = obj[:description]
|
29
|
+
@sort = (obj[:sort] || 19).to_i
|
30
|
+
@sumup = obj[:sumup] ? true : false
|
31
|
+
@data = (obj[:data] || []).map{|d| Item.new(d)}
|
32
|
+
@number = (obj[:number] || 0).to_i
|
33
|
+
@created_at = obj[:created_at] ? Time.strptime(obj[:created_at], GrowthForecast::TIME_FORMAT) : nil
|
34
|
+
@updated_at = obj[:updated_at] ? Time.strptime(obj[:updated_at], GrowthForecast::TIME_FORMAT) : nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def complex?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_json
|
42
|
+
{
|
43
|
+
'complex' => true,
|
44
|
+
'id' => @id,
|
45
|
+
'service_name' => @service_name, 'section_name' => @section_name, 'graph_name' => @graph_name,
|
46
|
+
'description' => @description, 'sort' => @sort, 'sumup' => @sumup,
|
47
|
+
'data' => @data.map(&:to_hash),
|
48
|
+
'number' => @number,
|
49
|
+
'created_at' => (@created_at ? @created_at.strftime(GrowthForecast::TIME_FORMAT) : nil),
|
50
|
+
'updated_at' => (@updated_at ? @updated_at.strftime(GrowthForecast::TIME_FORMAT) : nil),
|
51
|
+
}.to_json
|
52
|
+
end
|
53
|
+
|
54
|
+
class Item
|
55
|
+
attr_accessor :graph_id, :gmode, :stack, :type
|
56
|
+
|
57
|
+
def initialize(obj)
|
58
|
+
if obj.is_a?(String)
|
59
|
+
obj = JSON.parse(obj)
|
60
|
+
end
|
61
|
+
obj.keys.each do |k|
|
62
|
+
obj[k.to_sym] = obj.delete(k)
|
63
|
+
end
|
64
|
+
|
65
|
+
@graph_id = obj[:graph_id]
|
66
|
+
@gmode = obj[:gmode] || 'gauge'
|
67
|
+
@stack = (obj[:stack] || obj[:stack].nil?) ? true : false
|
68
|
+
@type = obj[:type] || 'AREA'
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_hash
|
72
|
+
{'graph_id' => @graph_id, 'gmode' => @gmode, 'stack' => @stack, 'type' => @type}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
class GrowthForecast::Graph
|
5
|
+
attr_accessor :id, :service_name, :section_name, :graph_name
|
6
|
+
attr_accessor :description, :mode, :sort, :color, :gmode
|
7
|
+
attr_accessor :type, :ulimit, :llimit, :stype, :sulimit, :sllimit
|
8
|
+
attr_accessor :adjust, :adjustval, :unit
|
9
|
+
attr_accessor :number, :data, :created_at, :updated_at
|
10
|
+
|
11
|
+
# TODO strict validations
|
12
|
+
|
13
|
+
def initialize(obj)
|
14
|
+
if obj.is_a?(String)
|
15
|
+
obj = JSON.parse(obj)
|
16
|
+
end
|
17
|
+
|
18
|
+
obj.keys.each do |k|
|
19
|
+
obj[k.to_sym] = obj.delete(k)
|
20
|
+
end
|
21
|
+
|
22
|
+
if obj[:complex]
|
23
|
+
raise ArgumentError, "complex graph is for GrowthForecast::Complex"
|
24
|
+
end
|
25
|
+
|
26
|
+
@id = obj[:id] ? obj[:id].to_i : nil
|
27
|
+
@service_name = obj[:service_name]
|
28
|
+
@section_name = obj[:section_name]
|
29
|
+
@graph_name = obj[:graph_name]
|
30
|
+
@description = obj[:description]
|
31
|
+
@mode = obj[:mode] || 'gauge'
|
32
|
+
@sort = (obj[:sort] || 19).to_i
|
33
|
+
@color = obj[:color]
|
34
|
+
@gmode = obj[:gmode] || 'gauge'
|
35
|
+
@type = obj[:type] || 'AREA'
|
36
|
+
@ulimit = obj[:ulimit] || 1000000000
|
37
|
+
@llimit = obj[:llimit] || -1000000000
|
38
|
+
@stype = obj[:stype] || 'AREA'
|
39
|
+
@sulimit = obj[:sulimit] || 100000
|
40
|
+
@sllimit = obj[:sllimit] || -100000
|
41
|
+
@adjust = obj[:adjust] || '*'
|
42
|
+
@adjustval = (obj[:adjustval] || 1).to_i
|
43
|
+
@unit = obj[:unit] || ''
|
44
|
+
@number = (obj[:number] || 0).to_i
|
45
|
+
@data = obj[:data] || []
|
46
|
+
@created_at = obj[:created_at] ? Time.strptime(obj[:created_at], GrowthForecast::TIME_FORMAT) : nil
|
47
|
+
@updated_at = obj[:updated_at] ? Time.strptime(obj[:updated_at], GrowthForecast::TIME_FORMAT) : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def complex?
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_json
|
55
|
+
if @color.nil?
|
56
|
+
raise RuntimeError, "cannot stringify as json without 'color' parameter"
|
57
|
+
end
|
58
|
+
|
59
|
+
{
|
60
|
+
'complex' => false,
|
61
|
+
'id' => @id,
|
62
|
+
'service_name' => @service_name, 'section_name' => @section_name, 'graph_name' => @graph_name,
|
63
|
+
'description' => @description, 'mode' => @mode, 'sort' => @sort, 'color' => @color, 'gmode' => @gmode,
|
64
|
+
'type' => @type, 'ulimit' => @ulimit, 'llimit' => @llimit, 'stype' => @stype, 'sulimit' => @sulimit, 'sllimit' => @sllimit,
|
65
|
+
'adjust' => @adjust, 'adjustval' => @adjustval, 'unit' => @unit,
|
66
|
+
'number' => @number, 'data' => @data,
|
67
|
+
'created_at' => @created_at.strftime(GrowthForecast::TIME_FORMAT), 'updated_at' => @updated_at.strftime(GrowthForecast::TIME_FORMAT)
|
68
|
+
}.to_json
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative './graph'
|
3
|
+
require_relative './complex'
|
4
|
+
|
5
|
+
class GrowthForecast::Path
|
6
|
+
attr_accessor :id, :service_name, :section_name, :graph_name
|
7
|
+
attr_accessor :complex
|
8
|
+
|
9
|
+
PATH_KEYS = [:id,:service_name,:section_name,:graph_name,:complex]
|
10
|
+
def self.path?(obj)
|
11
|
+
if obj.is_a?(String)
|
12
|
+
obj = JSON.parse(obj)
|
13
|
+
end
|
14
|
+
keys = obj.keys.size
|
15
|
+
if keys >= 4 && keys <= 5 && obj.keys.map(&:to_sym).reduce(true){|r,k| r && PATH_KEYS.include?(k)}
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(obj)
|
22
|
+
if obj.is_a?(String)
|
23
|
+
obj = JSON.parse(obj)
|
24
|
+
end
|
25
|
+
obj.keys.each do |k|
|
26
|
+
obj[k.to_sym] = obj.delete(k)
|
27
|
+
end
|
28
|
+
|
29
|
+
@id = obj[:id].to_i
|
30
|
+
@service_name = obj[:service_name]
|
31
|
+
@section_name = obj[:section_name]
|
32
|
+
@graph_name = obj[:graph_name]
|
33
|
+
@complex = obj[:complex] ? true : false
|
34
|
+
end
|
35
|
+
|
36
|
+
def complex?
|
37
|
+
@complex
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_json
|
41
|
+
{
|
42
|
+
'id' => @id, 'complex' => @complex,
|
43
|
+
'service_name' => @service_name, 'section_name' => @section_name, 'graph_name' => @graph_name,
|
44
|
+
}.to_json
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_graph
|
48
|
+
if @complex
|
49
|
+
GrowthForecast::Complex.new({
|
50
|
+
complex: true, id: @id, service_name: @service_name, section_name: @section_name, graph_name: @graph_name
|
51
|
+
})
|
52
|
+
else
|
53
|
+
GrowthForecast::Graph.new({
|
54
|
+
complex: false, id: @id, service_name: @service_name, section_name: @section_name, graph_name: @graph_name
|
55
|
+
})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require "growthforecast/version"
|
2
|
+
|
3
|
+
class GrowthForecast
|
4
|
+
TIME_FORMAT = '%Y/%m/%d %H:%M:%S'
|
5
|
+
end
|
6
|
+
|
7
|
+
require "growthforecast/graph"
|
8
|
+
require "growthforecast/complex"
|
9
|
+
require "growthforecast/path"
|
10
|
+
|
11
|
+
require 'net/http'
|
12
|
+
require 'uri'
|
13
|
+
require 'json'
|
14
|
+
|
15
|
+
class GrowthForecast
|
16
|
+
attr_accessor :host, :port, :prefix, :timeout, :debug
|
17
|
+
|
18
|
+
def initialize(host='localhost', port=5125, prefix=nil, timeout=30, debug=false)
|
19
|
+
@host = host
|
20
|
+
@port = port.to_i
|
21
|
+
@prefix = if prefix && (prefix =~ /^\//) then prefix
|
22
|
+
elsif prefix then '/' + prefix
|
23
|
+
else '/'
|
24
|
+
end
|
25
|
+
@prefix.chop! if @prefix =~ /\/$/
|
26
|
+
@timeout = timeout.to_i
|
27
|
+
@debug = debug ? true : false
|
28
|
+
end
|
29
|
+
|
30
|
+
def debug(mode=nil)
|
31
|
+
if mode.nil?
|
32
|
+
return GrowthForecast.new(@host,@port,@prefix,@timeout,true)
|
33
|
+
end
|
34
|
+
@mode = mode ? true : false
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def post(service, section, name, value, mode=nil, color=nil)
|
39
|
+
form = {'number' => value}
|
40
|
+
form.update({'mode' => mode}) if mode
|
41
|
+
form.update({'color' => color}) if color
|
42
|
+
content = URI.encode_www_form(form)
|
43
|
+
|
44
|
+
request('POST', "/api/#{service}/#{section}/#{name}", {}, content)
|
45
|
+
end
|
46
|
+
|
47
|
+
def by_name(service, section, name)
|
48
|
+
((self.tree()[service] || {})[section] || {})[name]
|
49
|
+
end
|
50
|
+
|
51
|
+
def graph(id)
|
52
|
+
if id.respond_to?(:complex?) && (not id.complex?) # accept graph object itself to reload
|
53
|
+
id = id.id
|
54
|
+
end
|
55
|
+
request('GET', "/json/graph/#{id}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def complex(id)
|
59
|
+
if id.respond_to?(:complex?) && id.complex? # accept complex object itself to reload
|
60
|
+
id = id.id
|
61
|
+
end
|
62
|
+
request('GET', "/json/complex/#{id}")
|
63
|
+
end
|
64
|
+
|
65
|
+
def graphs
|
66
|
+
request('GET', "/json/list/graph", {}, '', true)
|
67
|
+
end
|
68
|
+
|
69
|
+
def complexes
|
70
|
+
list = request('GET', "/json/list/complex", {}, '', true)
|
71
|
+
list.each do |path|
|
72
|
+
path.complex = true
|
73
|
+
end
|
74
|
+
list
|
75
|
+
end
|
76
|
+
|
77
|
+
def all
|
78
|
+
request('GET', "/json/list/all", {}, '', true)
|
79
|
+
end
|
80
|
+
|
81
|
+
def tree
|
82
|
+
root = {}
|
83
|
+
self.all().each do |i|
|
84
|
+
root[i.service_name] ||= {}
|
85
|
+
root[i.service_name][i.section_name] ||= {}
|
86
|
+
root[i.service_name][i.section_name][i.graph_name] = i
|
87
|
+
end
|
88
|
+
root
|
89
|
+
end
|
90
|
+
|
91
|
+
def edit(spec)
|
92
|
+
if spec.id.nil?
|
93
|
+
raise ArgumentError, "cannot edit graph without id (get graph data from GrowthForecast at first)"
|
94
|
+
end
|
95
|
+
path = if spec.complex?
|
96
|
+
"/json/edit/complex/#{spec.id}"
|
97
|
+
else
|
98
|
+
"/json/edit/graph/#{spec.id}"
|
99
|
+
end
|
100
|
+
request('POST', path, {}, spec.to_json)
|
101
|
+
end
|
102
|
+
|
103
|
+
def delete(spec)
|
104
|
+
if spec.id.nil?
|
105
|
+
raise ArgumentError, "cannot delete graph without id (get graph data from GrowthForecast at first)"
|
106
|
+
end
|
107
|
+
path = if spec.complex?
|
108
|
+
"/delete_complex/#{spec.id}"
|
109
|
+
else
|
110
|
+
"/delete/#{spec.service_name}/#{spec.section_name}/#{spec.graph_name}"
|
111
|
+
end
|
112
|
+
request('POST', path)
|
113
|
+
end
|
114
|
+
|
115
|
+
ADDITIONAL_PARAMS = ['description' 'sort' 'gmode' 'ulimit' 'llimit' 'sulimit' 'sllimit' 'type' 'stype' 'adjust' 'adjustval' 'unit']
|
116
|
+
def add(spec)
|
117
|
+
unless spec.respond_to?(:complex?)
|
118
|
+
raise ArgumentError, "parameter of add() must be instance of GrowthForecast::Graph or GrowthForecast::Complex (or use add_graph/add_complex)"
|
119
|
+
end
|
120
|
+
if spec.complex?
|
121
|
+
add_complex_spec(spec)
|
122
|
+
else
|
123
|
+
add_graph(spec.service_name, spec.section_name, spec.graph_name, spec.number, spec.color, spec.mode)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def add_graph(service, section, graph_name, initial_value=0, color=nil, mode=nil)
|
128
|
+
if service.empty? || section.empty? || graph_name.empty?
|
129
|
+
raise ArgumentError, "service, section and graph_name must be specified"
|
130
|
+
end
|
131
|
+
if (not color.nil?)
|
132
|
+
unless color =~ /^#[0-9a-fA-F]{6}/
|
133
|
+
raise ArgumentError, "color must be specified like #FFFFFF"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
post(service, section, graph_name, initial_value, mode, color) and true # 'add' and 'add_graph' returns boolean not graph object
|
137
|
+
end
|
138
|
+
|
139
|
+
def add_complex(service, section, graph_name, description, sumup, sort, type, gmode, stack, data_graph_ids)
|
140
|
+
unless data_graph_ids.is_a?(Array) && data_graph_ids.size > 0
|
141
|
+
raise ArgumentError, "To create complex graph, specify 1 or more sub graph ids"
|
142
|
+
end
|
143
|
+
unless sort >= 0 and sort <= 19
|
144
|
+
raise ArgumentError, "sort must be 0..19"
|
145
|
+
end
|
146
|
+
unless type == 'AREA' || type == 'LINE1' || type == 'LINE2'
|
147
|
+
raise ArgumentError, "type must be one of AREA/LINE1/LINE2"
|
148
|
+
end
|
149
|
+
unless gmode == 'gauge' || gmode == 'subtract'
|
150
|
+
raise ArgumentError, "gmode must be one of gauge/subtract"
|
151
|
+
end
|
152
|
+
spec = GrowthForecast::Complex.new({
|
153
|
+
complex: true,
|
154
|
+
service_name: service, section_name: section, graph_name: graph_name,
|
155
|
+
description: description, sumup: sumup, sort: sort,
|
156
|
+
data: data_graph_ids.map{|id| {'graph_id' => id, 'type' => type, 'gmode' => gmode, 'stack' => stack} }
|
157
|
+
})
|
158
|
+
add_complex_spec(spec)
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def add_complex_spec(spec)
|
164
|
+
request('POST', "/json/create/complex", {}, spec.to_json)
|
165
|
+
end
|
166
|
+
|
167
|
+
def concrete(obj)
|
168
|
+
case
|
169
|
+
when obj.nil?
|
170
|
+
nil
|
171
|
+
when obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
|
172
|
+
obj
|
173
|
+
when obj.is_a?(Array)
|
174
|
+
obj.map{|e| concrete(e)}
|
175
|
+
when GrowthForecast::Path.path?(obj)
|
176
|
+
GrowthForecast::Path.new(obj)
|
177
|
+
when obj['complex']
|
178
|
+
Complex.new(obj)
|
179
|
+
else
|
180
|
+
Graph.new(obj)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def request(method, path, header={}, content='', getlist=false)
|
185
|
+
concrete(http_request(method, path, header, content, getlist))
|
186
|
+
end
|
187
|
+
|
188
|
+
def http_request(method, path, header={}, content='', getlist=false)
|
189
|
+
conn = Net::HTTP.new(@host, @port)
|
190
|
+
conn.open_timeout = conn.read_timeout = @timeout
|
191
|
+
request_path = @prefix + path
|
192
|
+
res = conn.send_request(method, request_path, content, header)
|
193
|
+
|
194
|
+
unless res.is_a?(Net::HTTPSuccess)
|
195
|
+
return [] if getlist and res.code == '404'
|
196
|
+
|
197
|
+
# GrowthForecast returns 200 for validation and other errors. hmm...
|
198
|
+
if @debug
|
199
|
+
warn "GrowthForecast returns response code #{res.code}"
|
200
|
+
warn " request (#{method}) http://#{host}:#{port}#{request_path}"
|
201
|
+
warn " with content #{content}"
|
202
|
+
end
|
203
|
+
return nil
|
204
|
+
end
|
205
|
+
# code 200
|
206
|
+
return true if res.body.length < 1
|
207
|
+
|
208
|
+
obj = begin
|
209
|
+
JSON.parse(res.body)
|
210
|
+
rescue JSON::ParserError => e
|
211
|
+
warn "failed to parse response content as json, with error: #{e.message}"
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
return nil unless obj
|
215
|
+
|
216
|
+
if obj.is_a?(Array) # get valid list
|
217
|
+
return obj
|
218
|
+
end
|
219
|
+
|
220
|
+
# hash obj
|
221
|
+
if obj.has_key?('error') && obj['error'] != 0
|
222
|
+
warn "request ended with error:"
|
223
|
+
(obj['messages'] || {}).each do |key,msg|
|
224
|
+
warn " #{key}: #{msg}"
|
225
|
+
end
|
226
|
+
warn " request (#{method}) http://#{host}:#{port}#{request_path}"
|
227
|
+
warn " with content #{res.body}"
|
228
|
+
nil
|
229
|
+
elsif obj.has_key?('error') && obj.has_key?('data') # valid response, without any errors
|
230
|
+
obj['data']
|
231
|
+
else # bare growthforecast object
|
232
|
+
obj
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: growthforecast
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- TAGOMORI Satoshi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-03 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Client library and tool to update values, create/edit/delete graphs of
|
15
|
+
GrowthForecast
|
16
|
+
email:
|
17
|
+
- tagomoris@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- example/test1.rb
|
28
|
+
- growthforecast.gemspec
|
29
|
+
- lib/growthforecast.rb
|
30
|
+
- lib/growthforecast/complex.rb
|
31
|
+
- lib/growthforecast/graph.rb
|
32
|
+
- lib/growthforecast/path.rb
|
33
|
+
- lib/growthforecast/version.rb
|
34
|
+
homepage: https://github.com/tagomoris/rb-growthforecast
|
35
|
+
licenses: []
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.8.21
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: A client library for GrowthForecast
|
58
|
+
test_files: []
|