efigence-influxdb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|