tsdb_time_series 4.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OTY2MTQyZTM3YWMwYzc3YWZlOWQyNDk1ZTBhNWM5MGNjNzYzNmE2Zg==
5
+ data.tar.gz: !binary |-
6
+ YmVkMjlhN2U1ODM5ODBmZGIzYzRkNDY5ZGViYjk1MWI5NGU5YWE2OQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MzE5ZDk3OWNmMzMxZWYwOGFkNTM1YzA5OTdmNTg3NDI4MTNiNWE4NmYyNjIx
10
+ ZDZhNTc5ZDZhYjZiNTY4YzE4N2Q1NWI2MDg2NjE1ZmFhZjEwMmVmY2QyNjhm
11
+ ZmEwZmExNzU4MzgwMmU0N2E1ZjI4NjVmZWRlMDAwOWMyMDRlZTg=
12
+ data.tar.gz: !binary |-
13
+ ODNmOTdlODViODczNGQ1YzY1ZWEwMGM1NzQyNDExZjM1ODBhMGQ5OWNmNTQ2
14
+ NDRjNWVhYmUzZDUwZTAxMTg4NjhlMGQzZjY3MDQyNzAzN2YzZWY4Mzk5NTlj
15
+ NGQxZjBhMzQ0MjMzOWU5MzMxNzYxOGViMWRjOWNhMDUyMTdkYWM=
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.swp
19
+ *.swo
20
+ bin/
21
+ vendor/
22
+ .DS_Store
@@ -0,0 +1,17 @@
1
+ LineLength:
2
+ Enabled: true
3
+ Max: 128
4
+
5
+ Encoding:
6
+ Enabled: false
7
+
8
+ CaseIndentation:
9
+ Enabled: false
10
+
11
+ # Annoying warning when dealing with Unix timestamps
12
+ NumericLiterals:
13
+ Enabled: false
14
+
15
+ # MethodLength is superseded by Reek's TooManyStatements smell-finder.
16
+ MethodLength:
17
+ Enabled: false
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.3-p545
@@ -0,0 +1,38 @@
1
+ # v4.1.2
2
+ - Renamed gem to `tsdb_time_series` for public use. (`time_series` is already taken)
3
+ - Cleaned up README.md a little bit
4
+
5
+ # v4.1.1
6
+ - Bumped Rubocop to `~> 0.28.0` and updated dependencies
7
+ - Fixed new Rubocop warnings
8
+
9
+ # v4.1.0
10
+ - Bumped RSpec to `~> 3.0`
11
+ - Updated tests to follow betterspecs.org guidelines
12
+ - Added integration tests using Docker
13
+
14
+ # v4.0.0
15
+ - Added initial support for synthetic metrics through formula calculations.
16
+ - Dropped OpenTSDB 1.1 support (includes dropping ASCII output support!)
17
+ - Dropped client-side validation support (done by OpenTSDB now)
18
+
19
+ # v3.0.0
20
+ - Updated response signatures to include status code, result count, and explicit error messages.
21
+
22
+ # v2.4.0
23
+ - Added multi-query support (persistent, pipelined HTTP requests)
24
+
25
+ # v2.3.0
26
+ - Deprecrated client-side validation for OpenTSDB 2.0 clients
27
+
28
+ # v2.2.0
29
+ - Switched to Excon from HTTParty
30
+
31
+ # v2.1.0
32
+ - Upgraded to Ruby 1.9.3
33
+
34
+ # v2.0.1
35
+ - Added proper rate support
36
+
37
+ # v2.0.0
38
+ - Initial OpenTSDB 2.0 support
@@ -0,0 +1,22 @@
1
+ ## Contributing to TimeSeries
2
+
3
+ The best way to contribute code is to fork the main repo and send a pull request on GitHub.
4
+
5
+ Bug fixes should be done in the master branch. New features or major changes should be done in a feature branch. Alternatively, you can send a plain-text patch to the [mailing list](https://groups.google.com/forum/#!forum/opentsdb-clients).
6
+
7
+ Please break down your changes into as many small commits as possible.
8
+
9
+ Please respect the coding style of the code you're changing. TimeSeries uses Rubocop and reek to adhere to the Ruby style guide.
10
+
11
+ ## Pull Request Requirements
12
+
13
+ *Before* sending a pull request, please make sure your changes meet the following criteria:
14
+
15
+ - You have run `bundle exec rake build` and your code:
16
+ - Maintains code coverage remains at 100%
17
+ - Has fully been fully documented (`yard` coverage at 100%)
18
+ - Has no `reek` warnings
19
+ - Has no `rubocop` warnings
20
+ - All RSpec tests pass
21
+
22
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in time_series.gemspec
4
+ gemspec
@@ -0,0 +1,27 @@
1
+ # vim: ft=ruby
2
+ # More info at https://github.com/guard/guard#readme
3
+ #
4
+ # More info also at https://github.com/guard/guard-rspec -- this one in
5
+ # particular details configuration options such as whether to run all tests
6
+ # after a failing test starts passing
7
+
8
+ guard :rspec, cli: '--tag ~slow' do
9
+ watch(/^spec\/.+_spec\.rb/)
10
+ watch(/^lib\/(.+)\.rb$/) do |match|
11
+ %w(unit integration acceptance).map do |kind|
12
+ "spec/#{kind}/lib/#{match[1]}_spec.rb"
13
+ end
14
+ end
15
+ watch('spec/spec_helper.rb') { 'spec' }
16
+ watch(%r{^spec/(fixtures|resources)(/|.rb)}) { 'spec' }
17
+ end
18
+
19
+ guard :rubocop, all_on_start: false do
20
+ watch('Guardfile')
21
+ watch(/.+\.rb$/)
22
+ watch(/(?:.+\/)?\.rubocop\.yml$/) { |m| File.dirname(m[0]) }
23
+ end
24
+
25
+ guard :reek do
26
+ watch(/^lib\/(.+)\.rb$/)
27
+ end
@@ -0,0 +1,232 @@
1
+ ## tsdb_time_series
2
+
3
+ `tsdb_time_series` is a Ruby Gem for OpenTSDB that provides core tools when working with an OpenTSDB data store. With `tsdb_time_series`, you can search for registered metrics, tag keys and tag values, read from and write to the OpenTSDB, and submit multiple simultaneous queries to an OpenTSDB cluster.
4
+
5
+ ### Installation
6
+
7
+ Download the Gem from a standard Gem server like rubygems.org and install it:
8
+
9
+ gem install tsdb_time_series
10
+
11
+ Alternatively, build it from source and install it:
12
+
13
+ git clone https://github.com/opower/time_series.git
14
+ cd time_series
15
+ gem build tsdb_time_series.gemspec
16
+ gem install tsdb_time_series-4.1.3.gem
17
+
18
+ ### Usage
19
+ Once you have the OpenTSDB cluster set up, we can configure the TimeSeries Gem to talk to the API. The first step would be configuring a TimeSeries client. If no host is specified, the client connects to localhost by default. The client connects to port 4242 by default.
20
+
21
+ #### Configuring a TimeSeries Client
22
+
23
+ ```ruby
24
+ client = Opower::TimeSeries::TSClient.new('opentsdb.foo.com', 4242)
25
+ client.configure({ version: '2.0', dry_run: false, validation: true })
26
+ ```
27
+
28
+ Here is a table that lists options supported by the TimeSeries client:
29
+
30
+ | Option | Type | Description| Default value |
31
+ | ------------- | ------------- | ------------- | ------------- |
32
+ | :version | Float | version of the OpentSDB cluster the client will be talking to. If you wish to use the new 2.0 endpoints, set version to 2.0 or higher. | 2.0 |
33
+ | :dry_run | Boolean | If set to true, this gem will not run any commands, only output the generated URLs or calls to OpenTSDB. | false |
34
+ | :validation | Boolean | With this flag set to true, client performs a check to validate the metric name. | false |
35
+
36
+ #### Search for a registered metric/tagk/tagv
37
+
38
+ Using a properly configured client, you can search an OpenTSDB cluster to find suggestions for a metric, tag key, or tag value. This employs the `/api/suggest` end point of the OpenTSDB API and works as a simple namespace search. It is useful when you do not know what metric labels are being written to the OpenTSDB.
39
+
40
+ ```ruby
41
+ client.suggest('proc.stat.cpu') # suggest a metric
42
+ client.suggest('proc.stat.cpu', 'tagk') # suggest a tagk
43
+ client.suggest('proc.stat.cpu', 'tagv') # suggest a tagv
44
+ ```
45
+
46
+ #### Writing to OpenTSDB
47
+
48
+ You can use a TimeSeries client to push telnet/netcat style writes to OpenTSDB. If no hostname and port are specified, this gem defaults to 127.0.0.1:4242. To insert a metric into OpenTSDB using a configured client, create a new `Metric` object first and then use the `client.write` call as shown below :
49
+
50
+ ```ruby
51
+ metric_config = {
52
+ name: 'proc.stat.cpu',
53
+ timestamp: Time.now.to_i,
54
+ value: 10,
55
+ tags: { host: 'somehost.foo.com', type: 'iowait' }
56
+ }
57
+
58
+ metric = Opower::TimeSeries::Metric.new(metric_config)
59
+ client.write(metric)
60
+ ```
61
+
62
+
63
+ #### Reading from OpenTSDB
64
+
65
+ We can use a TimeSeries client to read metric data from an OpenTSDB cluster. To read data from OpenTSDB, you would first create a query object to run against the specified client. This query object supports all the standard options in the 2.0 API. Here is an example :
66
+
67
+ ```ruby
68
+ query_config = {
69
+ format: :png,
70
+ start: '2013/01/01-01:00:00',
71
+ end: '2013/02/01-01:00:00',
72
+ m: [{ aggregator: 'sum', metric: 'proc.stat.cpu', tags: { type: 'iowait' } }],
73
+ nocache: true
74
+ }
75
+
76
+ query = Opower::TimeSeries::Query.new(query_config)
77
+ client.run_query(query)
78
+ ```
79
+
80
+ The `Query` object accepts the following parameters:
81
+
82
+
83
+ | Option | Type | Description| Default value |
84
+ | ------------- | ------------- | ------------- | ------------- |
85
+ | :format | String | Specifies the output format. supported values include : `ascii`, `json`, `png`. | 'json' |
86
+ | :start | `String` / `Integer` / `DateTime` | The query's start date/time expressed as '2013/01/01-01:00:00' (string), '5m-ago' (String, indicating data for last 5 minutes), 1232323232 (time since epoch, Integer) or as a Ruby DateTime object. This is a required field. | none |
87
+ | :end | `String` / `Integer` / `DateTime` | The query's end date. This field supports the same types as `:start` field. This field is optional | Time.now (current time) |
88
+ | :m | `Array` | Array of JSON objects with the `aggregator`, `metrics`, and `tags` as fields: | none |
89
+
90
+ Here is a sample metrics object , that goes into the :m object .
91
+ ```ruby
92
+ m: [{ aggregator: 'sum', metric: 'proc.stat.cpu', tags: { type: 'iowait', version: 2.1 } }]
93
+ ```
94
+
95
+ Other options available to the REST API can be used here as well. Here is a list of options that have been tested to work with this gem. See the [OpenTSDB documentation](http://opentsdb.net/http-api.html#/q_Parameters) for more information :
96
+
97
+ ```
98
+
99
+ # * o Rendering options.
100
+ # * wxh The dimensions of the graph.
101
+ # * yrange The range of the left Y axis.
102
+ # * y2range The range of the right Y axis.
103
+ # * ylabel Label for the left Y axis.
104
+ # * y2label Label for the right Y axis.
105
+ # * yformat Format string for the left Y axis.
106
+ # * y2formatFormat string for the right Y axis.
107
+ # * ylog Enables log scale for the left Y axis.
108
+ # * y2log Enables log scale for the right Y axis.
109
+ # * key Options for the key (legend) of the graph.
110
+ # * nokey Removes the key (legend) from the graph.
111
+ # * nocache Forces TSD to ignore cache and fetch results from HBase.
112
+
113
+ ```
114
+
115
+
116
+
117
+ #### Example Queries
118
+
119
+ ```ruby
120
+ query_config = {
121
+ format: :ascii,
122
+ start: 14535353,
123
+ end: 16786786,
124
+ m: [{ aggregator: 'sum', metric: 'proc.stat.cpu', rate: true, tags: { type: 'iowait' } }]
125
+ }
126
+
127
+ query = Opower::TimeSeries::Query.new(query_config)
128
+ client.run_query(query)
129
+ ```
130
+
131
+ ```ruby
132
+ query_config = {
133
+ format: :json,
134
+ start: '3m-ago',
135
+ m: [{ aggregator: 'max', metric: 'proc.stat.cpu', tags: { type: 'iowait' } }],
136
+ nocache: true
137
+ }
138
+
139
+ query = Opower::TimeSeries::Query.new(query_config)
140
+ client.run_query(query)
141
+ ```
142
+
143
+ #### Running Multiple Queries Simultaneously
144
+
145
+ If you need to query multiple metrics at the same time, TimeSeries provides support for that as well:
146
+
147
+ ```ruby
148
+ queries = []
149
+ 3.times do
150
+ query_config = {
151
+ format: :ascii,
152
+ start: 14535353,
153
+ end: 16786786,
154
+ m: [{ aggregator: 'sum', metric: 'proc.stat.cpu', rate: true, tags: { type: 'iowait' } }]
155
+ }
156
+
157
+ queries << Opower::TimeSeries::Query.new(query_config)
158
+ end
159
+
160
+ client.run_queries(queries)
161
+ ```
162
+
163
+ #### Running Synthetic Metric Queries
164
+
165
+ Sometimes, you might need to create a new time series using metrics data from existing time series. We call these 'Synthetic Metric Queries'. Some examples could be disk utilization (expressed as disk used/total disk available) or CPU itilization (expressed as cpu cycles used/total cpu cycles).
166
+
167
+ TimeSeries also provides the capability to create synthetic metric queries through the use of a formula and any number of queries against OpenTSDB. Here is an example that creates a formula which adds two time series ( `x + y` ) and feeds the calculation with data from OpenTSDB :
168
+
169
+ ```ruby
170
+ metric_x = [{ metric: 'sys.numa.allocation', tags: { host: 'somehost.foo.com' } }]
171
+ query_config_x = { format: :json, start: '1h-ago', m: metric_x }
172
+ @query_metric_x = Opower::TimeSeries::Query.new(query_config_x)
173
+
174
+ metric_y = [{ metric: 'sys.numa.foreign_allocs', tags: { host: 'somehost.foo.com' } }]
175
+ query_config_y = { format: :json, start: '1h-ago', m: metric_y }
176
+ @query_metric_y = Opower::TimeSeries::Query.new(query_config_y)
177
+
178
+ name = 'My Synthetic Metric Alias'
179
+ formula = 'x + y'
180
+ query_hash = { x: @query_metric_x, y: @query_metric_y }
181
+ client.run_synthetic_query(name, formula, query_hash)
182
+ ```
183
+
184
+ The above example illustrates how you pass in a hash object to the client in order to run a sythentic query. This also indicates how the key maps to the parameters in the formula, with their corresponding values consisting of a Query object. When the calculation is performed, it will only operate on matching timestamps. If there are no matching data-points, it will return nothing.
185
+
186
+ For more information about what can be done with the formula parameters, read the documentation for the [Dentaku Calculator](https://github.com/rubysolo/dentaku). This gem expects any parameter in the formula to have a matching query in the query hash.
187
+
188
+ ##### Built-in Ruby Math support
189
+
190
+ ```ruby
191
+ name = 'My Synthetic Metric Alias'
192
+ formula = 'cos(x) + y'
193
+ query_hash = { x: @query_metric_x, y: @query_metric_y }
194
+ client.run_synthetic_query(name, formula, query_hash)
195
+ ```
196
+
197
+ Formulas in time-series can use all of the basic methods provided by the Math module from Ruby.
198
+
199
+ You must wrap nested mathematical expressions in formulas or Dentaku will attempt to pass them as separate arguments into the lambda below!
200
+
201
+ For example:
202
+ Assume x = 1, y = 2
203
+ - cos(x + y) is translated into cos(1, 'add', 2) - this calls Math.cos(1, 'add', 2) - this obviously throws an error
204
+ - cos((x + y)) is translated into cos(3) - this correctly calls Math.cos(3)
205
+
206
+ This is due to the way Dentaku handles the order of precedence; unless you wrap nested arguments, it will pass them separately.
207
+
208
+ #### Testing time_series
209
+
210
+ Test cases should be added for any new code added to this project.
211
+
212
+ Run acceptance/unit tests locally:
213
+
214
+ ```
215
+ rake spec
216
+ ```
217
+
218
+ Running integration tests:
219
+ ```
220
+ docker pull opower/opentsdb
221
+ rake integration
222
+ ```
223
+
224
+ Integration tests requires you have a `Docker` installed and have ran `docker pull opower/opentsdb` before-hand.
225
+
226
+ #### Generating Documentation
227
+
228
+ To generate the documentation for this gem, run the following:
229
+
230
+ ```
231
+ yard doc
232
+ ```
@@ -0,0 +1,44 @@
1
+ # rubocop: disable LeadingCommentSpace
2
+ #! /usr/bin/env rake
3
+ # rubocop: enable LeadingCommentSpace
4
+ require 'bundler/gem_tasks'
5
+ require 'yard'
6
+ require 'rspec/core/rake_task'
7
+ require 'reek/rake/task'
8
+ require 'rubocop/rake_task'
9
+
10
+ task default: :build
11
+
12
+ # If there are test failures, you'll need to write code to address them.
13
+ # So no point in continuing to run the style tests.
14
+ desc 'Runs unit/acceptance tests'
15
+ RSpec::Core::RakeTask.new(:spec) do |task|
16
+ task.pattern = 'spec/acceptance/**/*.rb,spec/unit/**/*.rb'
17
+ end
18
+
19
+ desc 'Runs integration tests'
20
+ RSpec::Core::RakeTask.new(:integration) do |task|
21
+ task.pattern = 'spec/integration/**/*.rb'
22
+ end
23
+
24
+ desc 'Runs yard'
25
+ YARD::Rake::YardocTask.new(:yard)
26
+
27
+ desc 'smells the lib directory, which Reek defaults to anyway'
28
+ Reek::Rake::Task.new(:reek) do |task|
29
+ task.verbose = true
30
+ end
31
+
32
+ desc 'smells the spec directory, which is less important than lib'
33
+ Reek::Rake::Task.new(:reek_tests) do |task|
34
+ task.source_files = 'spec/**/*.rb'
35
+ task.verbose = true
36
+ end
37
+
38
+ desc 'runs Rubocop'
39
+ RuboCop::RakeTask.new
40
+
41
+ desc 'Runs test and code cleanliness suite: Rubocop, Reek, rspec, and yard'
42
+ task run_guards: [:spec, :yard, :reek, :reek_tests, :rubocop]
43
+
44
+ task build: :run_guards
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'time_series/metric'
3
+ require 'time_series/query'
4
+ require 'time_series/results/result'
5
+ require 'time_series/results/synthetic_result'
6
+ require 'time_series/ts_client'
7
+ require 'time_series/version'
@@ -0,0 +1,56 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Opower
4
+ module TimeSeries
5
+ # Represents a metric that can be inserted into OpenTSDB instance through a [TSDBClient] object.
6
+ class Metric
7
+ attr_reader :name, :value, :timestamp, :tags
8
+
9
+ # Initializer for the Metric class.
10
+ #
11
+ # @param [Hash] config configuration hash consisting of the following values:
12
+ # @option config [String] :name The metric name (required)
13
+ # @option config [String] :value The metric value (required)
14
+ # @option config [String, Integer, Timestamp] :timestamp The timestamp in either epoch or a TimeStamp object.
15
+ # @option config [Array] :tags Array of tags to set for this metric. (tag_key => value)
16
+ #
17
+ # @return [Metric] a new Metric object
18
+ def initialize(config = {})
19
+ validate(config, [:name, :value])
20
+
21
+ @name = config[:name]
22
+ @value = config[:value]
23
+ @timestamp = config[:timestamp] || Time.now.to_i
24
+ @tags = config[:tags] || {}
25
+ end
26
+
27
+ # Converts the metric into the format required for use by `put` to insert into OpenTSDB.
28
+ #
29
+ # @return [String] put string
30
+ def to_s
31
+ result = ''
32
+ # Format the string for OpenTSDB
33
+ @tags.each { |key, value| result += "#{key}=#{value} " }
34
+ [@name, @timestamp, @value, result.rstrip].join(' ')
35
+ end
36
+
37
+ private
38
+
39
+ # Validates the metric inputs
40
+ #
41
+ # @param [Hash] config The configuration to validate.
42
+ # @param [Array] required_fields The required fields to be set inside the configuration.
43
+ def validate(config = {}, required_fields)
44
+ # Required fields check
45
+ required_fields.each do |field|
46
+ next if config.include?(field)
47
+ fail(ArgumentError, "#{field} is required to write into OpenTSDB.")
48
+ end
49
+
50
+ # Reject if user provided timestamp as not numeric
51
+ timestamp = config[:timestamp]
52
+ fail(ArgumentError, 'Timestamp must be numeric') if timestamp && !(timestamp.is_a? Fixnum)
53
+ end
54
+ end
55
+ end
56
+ end