quark 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/quark +53 -0
- data/lib/quark.rb +66 -0
- data/lib/quark/commands/fetch.rb +45 -0
- data/lib/quark/commands/observe.rb +11 -0
- data/lib/quark/commands/peek.rb +28 -0
- data/lib/quark/config.rb +18 -0
- metadata +107 -0
data/bin/quark
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require 'quark'
|
4
|
+
require 'quark/config'
|
5
|
+
|
6
|
+
module Quark
|
7
|
+
class CLI
|
8
|
+
class<<self
|
9
|
+
def run()
|
10
|
+
@options = {}
|
11
|
+
|
12
|
+
(OptionParser.new do |opts|
|
13
|
+
opts.banner = "Usage: quark [options]"
|
14
|
+
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
opts.on('-H', '--redis-host HOST', 'Hostname of the Redis server to write to') do |host|
|
17
|
+
Quark::Config.set("redis.host", host)
|
18
|
+
end
|
19
|
+
|
20
|
+
# -----------------------------------------------------------------------------
|
21
|
+
opts.on('-P', '--redis-port PORT', 'Hostname of the Redis server to write to') do |port|
|
22
|
+
Quark::Config.set("redis.port", port)
|
23
|
+
end
|
24
|
+
|
25
|
+
# -----------------------------------------------------------------------------
|
26
|
+
opts.on('-N', '--redis-prefix PREFIX', 'The string used to prefix keys in Redis') do |prefix|
|
27
|
+
Quark::Config.set("redis.prefix", prefix)
|
28
|
+
end
|
29
|
+
|
30
|
+
# -----------------------------------------------------------------------------
|
31
|
+
opts.on('-S', '--socket FILE', 'Location of the Unix socket to accept commands from') do |file|
|
32
|
+
Quark::Config.set("quark.socket", file)
|
33
|
+
end
|
34
|
+
|
35
|
+
# -----------------------------------------------------------------------------
|
36
|
+
opts.on('-a', '--listen-address ADDR', 'The IP address to listen on') do |addr|
|
37
|
+
Quark::Config.set("quark.address", addr)
|
38
|
+
end
|
39
|
+
|
40
|
+
# -----------------------------------------------------------------------------
|
41
|
+
opts.on('-p', '--listen-port PORT', 'The TCP port to listen on') do |port|
|
42
|
+
Quark::Config.set("quark.port", port)
|
43
|
+
end
|
44
|
+
|
45
|
+
end).parse!
|
46
|
+
|
47
|
+
Quark::Server.run()
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
Quark::CLI.run()
|
data/lib/quark.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'redis'
|
4
|
+
require 'hiredis'
|
5
|
+
require 'em-synchrony'
|
6
|
+
require 'multi_json'
|
7
|
+
require 'quark/config'
|
8
|
+
|
9
|
+
|
10
|
+
module Quark
|
11
|
+
module SocketServer
|
12
|
+
Dir[File.join(File.dirname(__FILE__), "quark", "commands", "*.rb")].each do |rb|
|
13
|
+
require "#{rb.gsub(/\.rb$/,'')}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def post_init()
|
17
|
+
@redis = Redis.new({
|
18
|
+
:host => Quark::Config.get("redis.host", "127.0.0.1"),
|
19
|
+
:port => Quark::Config.get("redis.port", 6379).to_i,
|
20
|
+
:db => Quark::Config.get("redis.db", 0).to_i
|
21
|
+
})
|
22
|
+
|
23
|
+
@_prefix = Quark::Config.get("redis.prefix", "quark")
|
24
|
+
end
|
25
|
+
|
26
|
+
def receive_data(data)
|
27
|
+
command, arguments = data.chomp.split(' ',2)
|
28
|
+
|
29
|
+
if methods.include?(:"process_command_#{command.downcase}")
|
30
|
+
rv = send(:"process_command_#{command.downcase}", arguments)
|
31
|
+
if rv.nil?
|
32
|
+
send_data(MultiJson.dump({
|
33
|
+
:success => true
|
34
|
+
})+"\n")
|
35
|
+
else
|
36
|
+
send_data(MultiJson.dump({
|
37
|
+
:success => true,
|
38
|
+
:command => data.chomp,
|
39
|
+
:results => rv
|
40
|
+
})+"\n")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def unbind()
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Server
|
50
|
+
def self.run()
|
51
|
+
@address = Quark::Config.get("quark.address", "127.0.0.1")
|
52
|
+
@port = Quark::Config.get("quark.port", 12161)
|
53
|
+
@filename = Quark::Config.get("quark.socket")
|
54
|
+
|
55
|
+
EM.run do
|
56
|
+
if not @filename.nil? and File.writable?(File.dirname(@filename))
|
57
|
+
puts "Starting server at #{@filename}..."
|
58
|
+
EM::start_server(@filename, Quark::SocketServer)
|
59
|
+
else
|
60
|
+
puts "Starting server on #{@address}:#{@port}..."
|
61
|
+
EM::start_server(@address, @port, Quark::SocketServer)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Quark
|
2
|
+
module SocketServer
|
3
|
+
def process_command_fetch(data)
|
4
|
+
rv = {}
|
5
|
+
keys, from, to = data.split(' ')
|
6
|
+
now = (Time.now.to_f * 1000).to_i
|
7
|
+
|
8
|
+
# negative from/to values are treated as relative number of seconds from now
|
9
|
+
if from.to_i < 0
|
10
|
+
from = (now - (-1 * from.to_i * 1000))
|
11
|
+
end
|
12
|
+
|
13
|
+
if to.to_i < 0
|
14
|
+
to = (now - (-1 * to.to_i * 1000))
|
15
|
+
end
|
16
|
+
|
17
|
+
# if not set, to defaults to now
|
18
|
+
to ||= now
|
19
|
+
|
20
|
+
# find all matching keys from all blocks
|
21
|
+
@redis.keys("#{@_prefix}:#{keys}:*").each do |key|
|
22
|
+
x, name, block = key.split(':',3)
|
23
|
+
block = block.to_i
|
24
|
+
|
25
|
+
# skip keys whose block falls outside of the given time range (if given)
|
26
|
+
next if block > 0 and not from.nil? and block < (from.to_i/1000/3600).to_i
|
27
|
+
next if block > 0 and not to.nil? and block > (to.to_i/1000/3600).to_i
|
28
|
+
|
29
|
+
# get all values, sort by timestamp ascending, and append to ouput (within range, if given)
|
30
|
+
@redis.hgetall(key).sort{|a,b|
|
31
|
+
a[0] <=> b[0]
|
32
|
+
}.each do |timestamp, value|
|
33
|
+
timestamp = timestamp.to_i
|
34
|
+
|
35
|
+
if timestamp >= from.to_i and timestamp <= to.to_i
|
36
|
+
rv[name] ||= []
|
37
|
+
rv[name] << [timestamp, value.to_f]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
return rv
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Quark
|
2
|
+
module SocketServer
|
3
|
+
def process_command_observe(data)
|
4
|
+
id, value, timestamp = data.chomp.split(' ')
|
5
|
+
block = Integer(timestamp.to_i / 1000 / 3600)
|
6
|
+
|
7
|
+
@redis.hset("#{@_prefix}:#{id}:#{block}", timestamp, value)
|
8
|
+
return nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Quark
|
2
|
+
module SocketServer
|
3
|
+
def process_command_peek(data)
|
4
|
+
rv = {}
|
5
|
+
keys = @redis.keys("#{@_prefix}:#{data}:*")
|
6
|
+
peek_keys = []
|
7
|
+
|
8
|
+
# get unique metric names
|
9
|
+
keys.collect{|i|
|
10
|
+
i.split(':')[1]
|
11
|
+
}.uniq().each do |name|
|
12
|
+
# get most recent bucket for this metric
|
13
|
+
peek_keys << keys.select{|i|
|
14
|
+
i.split(':')[1] == name
|
15
|
+
}.sort.last
|
16
|
+
end
|
17
|
+
|
18
|
+
# now that we have the most recent bucket, pull the most recent value from that bucket
|
19
|
+
peek_keys.each do |peek|
|
20
|
+
rv[peek.split(':')[1]] = @redis.hgetall(peek).sort{|a,b|
|
21
|
+
a[0] <=> b[0]
|
22
|
+
}.last
|
23
|
+
end
|
24
|
+
|
25
|
+
return rv
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/quark/config.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Quark
|
2
|
+
class Config
|
3
|
+
require 'hashlib'
|
4
|
+
@_options = Hash.new()
|
5
|
+
|
6
|
+
def self.get(key, fallback=nil)
|
7
|
+
@_options.rget(key, fallback)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.set(key, value)
|
11
|
+
@_options.rset(key, value)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.all()
|
15
|
+
@_options
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: quark
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Gary Hetzel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: multi_json
|
16
|
+
requirement: &14255880 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - =
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.7.9
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *14255880
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: eventmachine
|
27
|
+
requirement: &14253980 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *14253980
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: redis
|
38
|
+
requirement: &14252900 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 3.0.0
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *14252900
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: hiredis
|
49
|
+
requirement: &14251500 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.5.2
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *14251500
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: em-synchrony
|
60
|
+
requirement: &14265760 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.0.3
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *14265760
|
69
|
+
description: A small service for logging and retrieving timeseries metrics into a
|
70
|
+
Redis server
|
71
|
+
email: garyhetzel@gmail.com
|
72
|
+
executables:
|
73
|
+
- quark
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- lib/quark.rb
|
78
|
+
- lib/quark/config.rb
|
79
|
+
- lib/quark/commands/peek.rb
|
80
|
+
- lib/quark/commands/fetch.rb
|
81
|
+
- lib/quark/commands/observe.rb
|
82
|
+
- bin/quark
|
83
|
+
homepage: https://github.com/ghetzel/quark
|
84
|
+
licenses: []
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.8.11
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: A miniscule timeseries metrics logging service
|
107
|
+
test_files: []
|