efigence-influxdb 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rubocop.yml +41 -0
- data/.travis.yml +11 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +362 -0
- data/Rakefile +22 -0
- data/efigence-influxdb.gemspec +28 -0
- data/lib/influxdb.rb +21 -0
- data/lib/influxdb/client.rb +77 -0
- data/lib/influxdb/client/http.rb +98 -0
- data/lib/influxdb/config.rb +60 -0
- data/lib/influxdb/errors.rb +40 -0
- data/lib/influxdb/logging.rb +22 -0
- data/lib/influxdb/max_queue.rb +18 -0
- data/lib/influxdb/point_value.rb +31 -0
- data/lib/influxdb/query/cluster.rb +17 -0
- data/lib/influxdb/query/continuous_query.rb +36 -0
- data/lib/influxdb/query/core.rb +109 -0
- data/lib/influxdb/query/database.rb +21 -0
- data/lib/influxdb/query/series.rb +13 -0
- data/lib/influxdb/query/shard.rb +14 -0
- data/lib/influxdb/query/shard_space.rb +60 -0
- data/lib/influxdb/query/user.rb +38 -0
- data/lib/influxdb/version.rb +3 -0
- data/lib/influxdb/writer/async.rb +115 -0
- data/lib/influxdb/writer/udp.rb +21 -0
- data/spec/influxdb/cases/async_client_spec.rb +33 -0
- data/spec/influxdb/cases/query_cluster_spec.rb +65 -0
- data/spec/influxdb/cases/query_database_spec.rb +58 -0
- data/spec/influxdb/cases/query_series_spec.rb +50 -0
- data/spec/influxdb/cases/query_shard_spec.rb +43 -0
- data/spec/influxdb/cases/query_user_spec.rb +127 -0
- data/spec/influxdb/cases/querying_spec.rb +159 -0
- data/spec/influxdb/cases/retry_requests_spec.rb +97 -0
- data/spec/influxdb/cases/udp_client_spec.rb +21 -0
- data/spec/influxdb/cases/write_points_spec.rb +141 -0
- data/spec/influxdb/client_spec.rb +58 -0
- data/spec/influxdb/config_spec.rb +118 -0
- data/spec/influxdb/logging_spec.rb +48 -0
- data/spec/influxdb/max_queue_spec.rb +29 -0
- data/spec/influxdb/point_value_spec.rb +66 -0
- data/spec/influxdb/worker_spec.rb +23 -0
- data/spec/spec_helper.rb +8 -0
- metadata +192 -0
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
targeted_files = ARGV.drop(1)
|
4
|
+
file_pattern = targeted_files.empty? ? 'spec/**/*_spec.rb' : targeted_files
|
5
|
+
|
6
|
+
require 'rspec/core'
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
10
|
+
t.pattern = FileList[file_pattern]
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.color = true
|
15
|
+
config.formatter = :documentation
|
16
|
+
end
|
17
|
+
|
18
|
+
task default: :spec
|
19
|
+
|
20
|
+
task :console do
|
21
|
+
sh 'pry -r ./lib/influxdb.rb'
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'influxdb/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "efigence-influxdb"
|
8
|
+
spec.version = InfluxDB::VERSION
|
9
|
+
spec.authors = ["Jacek Grzybowski", "Todd Persen"]
|
10
|
+
spec.email = ["jgrzybowski@efigence.com"]
|
11
|
+
spec.description = %q{This is a fork of official Ruby library for InfluxDB meant for v0.9.x support.}
|
12
|
+
spec.summary = %q{Ruby library for InfluxDB.}
|
13
|
+
spec.homepage = "https://github.com/efigence/influxdb-ruby/tree/support_v0.9.x"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "json"
|
22
|
+
spec.add_runtime_dependency "cause"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0.0"
|
27
|
+
spec.add_development_dependency "webmock"
|
28
|
+
end
|
data/lib/influxdb.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "influxdb/version"
|
2
|
+
require "influxdb/errors"
|
3
|
+
require "influxdb/logging"
|
4
|
+
require "influxdb/max_queue"
|
5
|
+
require "influxdb/point_value"
|
6
|
+
require "influxdb/config"
|
7
|
+
|
8
|
+
require "influxdb/writer/async"
|
9
|
+
require "influxdb/writer/udp"
|
10
|
+
|
11
|
+
require "influxdb/query/core"
|
12
|
+
require "influxdb/query/cluster"
|
13
|
+
require "influxdb/query/database"
|
14
|
+
require "influxdb/query/series"
|
15
|
+
require "influxdb/query/shard"
|
16
|
+
require "influxdb/query/user"
|
17
|
+
require "influxdb/query/continuous_query"
|
18
|
+
require "influxdb/query/shard_space"
|
19
|
+
|
20
|
+
require "influxdb/client/http"
|
21
|
+
require "influxdb/client"
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'cause'
|
3
|
+
|
4
|
+
module InfluxDB
|
5
|
+
# rubocop:disable Metrics/MethodLength
|
6
|
+
# rubocop:disable Metrics/AbcSize
|
7
|
+
|
8
|
+
# InfluxDB client class
|
9
|
+
class Client
|
10
|
+
attr_reader :config, :writer
|
11
|
+
|
12
|
+
include InfluxDB::Logging
|
13
|
+
include InfluxDB::HTTP
|
14
|
+
include InfluxDB::Query::Core
|
15
|
+
include InfluxDB::Query::Cluster
|
16
|
+
include InfluxDB::Query::Database
|
17
|
+
include InfluxDB::Query::Shard
|
18
|
+
include InfluxDB::Query::Series
|
19
|
+
include InfluxDB::Query::User
|
20
|
+
include InfluxDB::Query::ContinuousQuery
|
21
|
+
include InfluxDB::Query::ShardSpace
|
22
|
+
|
23
|
+
# Initializes a new InfluxDB client
|
24
|
+
#
|
25
|
+
# === Examples:
|
26
|
+
#
|
27
|
+
# # connect to localhost using root/root
|
28
|
+
# # as the credentials and doesn't connect to a db
|
29
|
+
#
|
30
|
+
# InfluxDB::Client.new
|
31
|
+
#
|
32
|
+
# # connect to localhost using root/root
|
33
|
+
# # as the credentials and 'db' as the db name
|
34
|
+
#
|
35
|
+
# InfluxDB::Client.new 'db'
|
36
|
+
#
|
37
|
+
# # override username, other defaults remain unchanged
|
38
|
+
#
|
39
|
+
# InfluxDB::Client.new username: 'username'
|
40
|
+
#
|
41
|
+
# # override username, use 'db' as the db name
|
42
|
+
# Influxdb::Client.new 'db', username: 'username'
|
43
|
+
#
|
44
|
+
# === Valid options in hash
|
45
|
+
#
|
46
|
+
# +:host+:: the hostname to connect to
|
47
|
+
# +:port+:: the port to connect to
|
48
|
+
# +:path+:: the specified path prefix when building the url e.g.: /prefix/db/dbname...
|
49
|
+
# +:username+:: the username to use when executing commands
|
50
|
+
# +:password+:: the password associated with the username
|
51
|
+
# +:use_ssl+:: use ssl to connect
|
52
|
+
def initialize(*args)
|
53
|
+
opts = args.last.is_a?(Hash) ? args.last : {}
|
54
|
+
opts[:database] = args.first if args.first.is_a? String
|
55
|
+
@config = InfluxDB::Config.new(opts)
|
56
|
+
@stopped = false
|
57
|
+
|
58
|
+
@writer = self
|
59
|
+
|
60
|
+
if config.async?
|
61
|
+
@writer = InfluxDB::Writer::Async.new(self, config.async)
|
62
|
+
elsif config.udp?
|
63
|
+
@writer = InfluxDB::Writer::UDP.new(self, config.udp)
|
64
|
+
end
|
65
|
+
|
66
|
+
at_exit { stop! } if config.retry > 0
|
67
|
+
end
|
68
|
+
|
69
|
+
def stop!
|
70
|
+
@stopped = true
|
71
|
+
end
|
72
|
+
|
73
|
+
def stopped?
|
74
|
+
@stopped
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'net/http'
|
4
|
+
require 'net/https'
|
5
|
+
|
6
|
+
module InfluxDB
|
7
|
+
# rubocop:disable Metrics/MethodLength
|
8
|
+
# rubocop:disable Metrics/AbcSize
|
9
|
+
module HTTP # :nodoc:
|
10
|
+
def get(url, options = {})
|
11
|
+
connect_with_retry do |http|
|
12
|
+
response = do_request http, Net::HTTP::Get.new(url)
|
13
|
+
if response.is_a? Net::HTTPSuccess
|
14
|
+
handle_successful_response(response, options)
|
15
|
+
elsif response.is_a? Net::HTTPUnauthorized
|
16
|
+
fail InfluxDB::AuthenticationError, response.body
|
17
|
+
else
|
18
|
+
resolve_error(response.body)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def post(url, data)
|
24
|
+
headers = {"Content-Type" => "application/octet-stream"}
|
25
|
+
connect_with_retry do |http|
|
26
|
+
response = do_request http, Net::HTTP::Post.new(url, headers), data
|
27
|
+
if response.is_a? Net::HTTPSuccess
|
28
|
+
return response
|
29
|
+
elsif response.is_a? Net::HTTPUnauthorized
|
30
|
+
fail InfluxDB::AuthenticationError, response.body
|
31
|
+
else
|
32
|
+
resolve_error(response.body)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def connect_with_retry(&block)
|
40
|
+
hosts = config.hosts.dup
|
41
|
+
delay = config.initial_delay
|
42
|
+
retry_count = 0
|
43
|
+
|
44
|
+
begin
|
45
|
+
hosts.push(host = hosts.shift)
|
46
|
+
http = Net::HTTP.new(host, config.port)
|
47
|
+
http.open_timeout = config.open_timeout
|
48
|
+
http.read_timeout = config.read_timeout
|
49
|
+
http.use_ssl = config.use_ssl
|
50
|
+
block.call(http)
|
51
|
+
|
52
|
+
rescue Timeout::Error, *InfluxDB::NET_HTTP_EXCEPTIONS => e
|
53
|
+
retry_count += 1
|
54
|
+
if (config.retry == -1 || retry_count <= config.retry) && !stopped?
|
55
|
+
log :error, "Failed to contact host #{host}: #{e.inspect} - retrying in #{delay}s."
|
56
|
+
sleep delay
|
57
|
+
delay = [config.max_delay, delay * 2].min
|
58
|
+
retry
|
59
|
+
else
|
60
|
+
raise e, "Tried #{retry_count - 1} times to reconnect but failed."
|
61
|
+
end
|
62
|
+
ensure
|
63
|
+
http.finish if http.started?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def do_request(http, req, data = nil)
|
68
|
+
req.basic_auth config.username, config.password if basic_auth?
|
69
|
+
req.body = data if data
|
70
|
+
http.request(req)
|
71
|
+
end
|
72
|
+
|
73
|
+
def basic_auth?
|
74
|
+
config.auth_method == 'basic_auth'
|
75
|
+
end
|
76
|
+
|
77
|
+
def resolve_error(response)
|
78
|
+
if response =~ /Couldn\'t find series/
|
79
|
+
fail InfluxDB::SeriesNotFound, response
|
80
|
+
else
|
81
|
+
fail InfluxDB::Error, response
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def handle_successful_response(response, options)
|
86
|
+
parsed_response = JSON.parse(response.body) if response.body
|
87
|
+
errors = errors_from_response(parsed_response) if parsed_response
|
88
|
+
raise InfluxDB::QueryError.new errors if errors
|
89
|
+
options.fetch(:parse, false) ? parsed_response : response
|
90
|
+
end
|
91
|
+
|
92
|
+
def errors_from_response(parsed_resp)
|
93
|
+
parsed_resp.is_a?(Hash) && parsed_resp.fetch('results', [])
|
94
|
+
.fetch(0, {})
|
95
|
+
.fetch('error', nil)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module InfluxDB
|
2
|
+
# InfluxDB client configuration
|
3
|
+
class Config
|
4
|
+
AUTH_METHODS = %w(params basic_auth)
|
5
|
+
|
6
|
+
attr_accessor :hosts,
|
7
|
+
:port,
|
8
|
+
:username,
|
9
|
+
:password,
|
10
|
+
:database,
|
11
|
+
:time_precision,
|
12
|
+
:use_ssl,
|
13
|
+
:auth_method,
|
14
|
+
:initial_delay,
|
15
|
+
:max_delay,
|
16
|
+
:open_timeout,
|
17
|
+
:read_timeout,
|
18
|
+
:retry,
|
19
|
+
:denormalize
|
20
|
+
|
21
|
+
attr_reader :async, :udp
|
22
|
+
|
23
|
+
# rubocop:disable all
|
24
|
+
def initialize(opts = {})
|
25
|
+
@database = opts[:database]
|
26
|
+
@hosts = Array(opts[:hosts] || opts[:host] || ["localhost"])
|
27
|
+
@port = opts.fetch(:port, 8086)
|
28
|
+
@username = opts.fetch(:username, "root")
|
29
|
+
@password = opts.fetch(:password, "root")
|
30
|
+
@auth_method = AUTH_METHODS.include?(opts[:auth_method]) ? opts[:auth_method] : "params"
|
31
|
+
@use_ssl = opts.fetch(:use_ssl, false)
|
32
|
+
@time_precision = opts.fetch(:time_precision, "s")
|
33
|
+
@initial_delay = opts.fetch(:initial_delay, 0.01)
|
34
|
+
@max_delay = opts.fetch(:max_delay, 30)
|
35
|
+
@open_timeout = opts.fetch(:write_timeout, 5)
|
36
|
+
@read_timeout = opts.fetch(:read_timeout, 300)
|
37
|
+
@async = opts.fetch(:async, false)
|
38
|
+
@udp = opts.fetch(:udp, false)
|
39
|
+
@retry = opts.fetch(:retry, nil)
|
40
|
+
@denormalize = opts.fetch(:denormalize, true)
|
41
|
+
@retry =
|
42
|
+
case @retry
|
43
|
+
when Integer
|
44
|
+
@retry
|
45
|
+
when true, nil
|
46
|
+
-1
|
47
|
+
when false
|
48
|
+
0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def udp?
|
53
|
+
!!udp
|
54
|
+
end
|
55
|
+
|
56
|
+
def async?
|
57
|
+
!!async
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "zlib"
|
3
|
+
|
4
|
+
module InfluxDB # :nodoc:
|
5
|
+
class Error < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class AuthenticationError < Error
|
9
|
+
end
|
10
|
+
|
11
|
+
class ConnectionError < Error
|
12
|
+
end
|
13
|
+
|
14
|
+
class SeriesNotFound < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
class JSONParserError < Error
|
18
|
+
end
|
19
|
+
|
20
|
+
class QueryError < Error
|
21
|
+
end
|
22
|
+
|
23
|
+
# Taken from: https://github.com/lostisland/faraday/blob/master/lib/faraday/adapter/net_http.rb
|
24
|
+
NET_HTTP_EXCEPTIONS = [
|
25
|
+
EOFError,
|
26
|
+
Errno::ECONNABORTED,
|
27
|
+
Errno::ECONNREFUSED,
|
28
|
+
Errno::ECONNRESET,
|
29
|
+
Errno::EHOSTUNREACH,
|
30
|
+
Errno::EINVAL,
|
31
|
+
Errno::ENETUNREACH,
|
32
|
+
Net::HTTPBadResponse,
|
33
|
+
Net::HTTPHeaderSyntaxError,
|
34
|
+
Net::ProtocolError,
|
35
|
+
SocketError,
|
36
|
+
Zlib::GzipFile::Error
|
37
|
+
]
|
38
|
+
|
39
|
+
NET_HTTP_EXCEPTIONS << OpenSSL::SSL::SSLError if defined?(OpenSSL)
|
40
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module InfluxDB
|
4
|
+
module Logging # :nodoc:
|
5
|
+
PREFIX = "[InfluxDB] "
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_writer :logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.logger
|
12
|
+
return false if @logger == false
|
13
|
+
@logger ||= ::Logger.new(STDERR).tap { |logger| logger.level = Logger::INFO }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def log(level, message)
|
19
|
+
InfluxDB::Logging.logger.send(level.to_sym, PREFIX + message) if InfluxDB::Logging.logger
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module InfluxDB
|
4
|
+
# Queue with max length limit
|
5
|
+
class MaxQueue < Queue
|
6
|
+
attr_reader :max
|
7
|
+
|
8
|
+
def initialize(max = 10_000)
|
9
|
+
fail ArgumentError, "queue size must be positive" unless max > 0
|
10
|
+
@max = max
|
11
|
+
super()
|
12
|
+
end
|
13
|
+
|
14
|
+
def push(obj)
|
15
|
+
super if length < @max
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module InfluxDB
|
2
|
+
class PointValue
|
3
|
+
attr_reader :series, :values, :tags, :timestamp
|
4
|
+
|
5
|
+
def initialize(data)
|
6
|
+
@series = data[:series].gsub(/\s/, '\ ')
|
7
|
+
@values = stringify(data[:values])
|
8
|
+
@tags = stringify(data[:tags])
|
9
|
+
@timestamp = data[:timestamp]
|
10
|
+
end
|
11
|
+
|
12
|
+
def dump
|
13
|
+
dump = "#{@series}"
|
14
|
+
dump << ",#{@tags}" if @tags
|
15
|
+
dump << " #{@values}"
|
16
|
+
dump << " #{@timestamp}" if @timestamp
|
17
|
+
dump
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def stringify(hash)
|
23
|
+
return nil unless hash && !hash.empty?
|
24
|
+
hash.map do |k,v|
|
25
|
+
key = k.to_s.gsub(/\s/, '\ ')
|
26
|
+
val = v.is_a?(String) ? v.gsub(/\s/, '\ ') : v
|
27
|
+
"#{key}=#{val}"
|
28
|
+
end.join(',')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module InfluxDB
|
2
|
+
module Query
|
3
|
+
module Cluster # :nodoc:
|
4
|
+
def create_cluster_admin(username, password)
|
5
|
+
execute("CREATE USER #{username} WITH PASSWORD '#{password}' WITH ALL PRIVILEGES")
|
6
|
+
end
|
7
|
+
|
8
|
+
def list_cluster_admins
|
9
|
+
list_users.select{|u| u['admin']}.map {|u| u.delete_if {|k,_| k == 'admin'}}
|
10
|
+
end
|
11
|
+
|
12
|
+
def revoke_cluster_admin_privileges(username)
|
13
|
+
execute("REVOKE ALL PRIVILEGES FROM #{username}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|