matt 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/README.md +115 -0
- data/bin/matt +4 -0
- data/lib/matt.rb +39 -0
- data/lib/matt/command.rb +211 -0
- data/lib/matt/configuration.rb +69 -0
- data/lib/matt/datasource.rb +14 -0
- data/lib/matt/datasource/sql.rb +33 -0
- data/lib/matt/exporter.rb +10 -0
- data/lib/matt/exporter/sql.rb +90 -0
- data/lib/matt/measure.rb +25 -0
- data/lib/matt/support.rb +1 -0
- data/lib/matt/support/puts.rb +26 -0
- data/lib/matt/version.rb +8 -0
- metadata +177 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 712f1cd0be5efa12e62fe0491c0c14028b76a5e4edcdaf792bf8ab9652397218
|
4
|
+
data.tar.gz: e133df58cfba2501cdd806668ff6e21363150a32a9563a6fa554938dc2bfed5c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ae322c7f52f9bab450bb21781b5653bd87a6e40703fadfe2768cc56305508464945f411c15584b3702cfefc969baf2af0fe4e437b6b454355b934b7602d39920
|
7
|
+
data.tar.gz: d4ef94dc91c994bb4886a4b9c4beb0c863af6d62bc2c66c440fb0a594788640bca2c536bbe1f86a8f3e22ebb93bc357ef09e0e29c604256aa4f064b167ba33ef
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# Matt - A monitoring tool for data-driven business decisions
|
2
|
+
|
3
|
+
Matt is a CTO-oriented tool that helps capturing daily data points that are
|
4
|
+
relevant for conducting growth hacking and other data-driven decisions.
|
5
|
+
|
6
|
+
It consists of the following abstractions:
|
7
|
+
|
8
|
+
* Datasource: provides access to data from which measures are extracted
|
9
|
+
* Measure: standardized data vector for relevant decisions
|
10
|
+
* Exporter: a way to save measures for future analysis and visualization
|
11
|
+
|
12
|
+
## How to install Matt?
|
13
|
+
|
14
|
+
There are various usage scenario to use matt, according to your needs and
|
15
|
+
types of environment and datasources.
|
16
|
+
|
17
|
+
### As a commandline
|
18
|
+
|
19
|
+
To simply use Matt as a commandline and connect to local databases, simply
|
20
|
+
install and use the ruby gem:
|
21
|
+
|
22
|
+
```
|
23
|
+
gem install matt
|
24
|
+
matt --help
|
25
|
+
```
|
26
|
+
|
27
|
+
### As a docker image
|
28
|
+
|
29
|
+
If you don't have ruby installed and want to use docker instead, we provide a
|
30
|
+
docker image that has Matt as entrypoint.
|
31
|
+
|
32
|
+
```
|
33
|
+
docker run enspirit/matt --help
|
34
|
+
```
|
35
|
+
|
36
|
+
Please check docker's network documentation if you want Matt to connect to
|
37
|
+
databases running on your host.
|
38
|
+
|
39
|
+
### In a Gemfile
|
40
|
+
|
41
|
+
It may be useful to use Matt as part of a larger ruby gemset, for instance
|
42
|
+
because you need various ruby gems to connect to mysql, postgres, or whatever.
|
43
|
+
|
44
|
+
Then in your Gemfile:
|
45
|
+
|
46
|
+
```
|
47
|
+
source https://rubygems.org
|
48
|
+
|
49
|
+
gem "matt"
|
50
|
+
```
|
51
|
+
|
52
|
+
Then,
|
53
|
+
|
54
|
+
```
|
55
|
+
bundle exec matt --help
|
56
|
+
```
|
57
|
+
|
58
|
+
### As a docker base image + a Gemfile
|
59
|
+
|
60
|
+
If Matt is part of a larger docker/kubernetes infrastructure, you probably need
|
61
|
+
the last scenario while also having your own docker image for the component.
|
62
|
+
|
63
|
+
This is a good basis for a Dockerfile:
|
64
|
+
|
65
|
+
```
|
66
|
+
FROM enspirit/matt # or from ruby-3.0:alpine
|
67
|
+
|
68
|
+
COPY Gemfile .
|
69
|
+
RUN bundle install
|
70
|
+
|
71
|
+
ENTRYPOINT ['bundle', 'exec', 'matt']
|
72
|
+
```
|
73
|
+
|
74
|
+
Let's say you build that image as `my/matt`, you can now simply run matt as
|
75
|
+
follows:
|
76
|
+
|
77
|
+
```
|
78
|
+
docker run my/matt --help
|
79
|
+
```
|
80
|
+
|
81
|
+
In such a scenario, you may want to use Matt's version that exists in the
|
82
|
+
docker container instead without having to specify it in the Gemfile.
|
83
|
+
The following Gemfile hack works:
|
84
|
+
|
85
|
+
```
|
86
|
+
gem "matt", path: "/matt"
|
87
|
+
```
|
88
|
+
|
89
|
+
## Semantics versioning
|
90
|
+
|
91
|
+
Matt uses semantics versioning since 1.0.
|
92
|
+
|
93
|
+
The public API consists in:
|
94
|
+
|
95
|
+
* Matt public methods
|
96
|
+
* Matt::Configuration public methods
|
97
|
+
* Matt::Datasource public & protected methods exposed to including classes
|
98
|
+
* Matt::Exporter (same)
|
99
|
+
* Matt::Measure (same)
|
100
|
+
* `matt` commandline and its options
|
101
|
+
* `enspirit/matt` docker image with `/matt` and `/home/matt` behavior
|
102
|
+
|
103
|
+
## Contribute
|
104
|
+
|
105
|
+
Please use github issues and pull requests for all questions, bug reports,
|
106
|
+
and contributions. Don't hesitate to get in touch with us with an early code
|
107
|
+
spike if you plan to add non trivial features.
|
108
|
+
|
109
|
+
## Licence
|
110
|
+
|
111
|
+
This software is distributed by Enspirit SRL under a MIT Licence. Please
|
112
|
+
contact Bernard Lambeau (blambeau@gmail.com) with any question.
|
113
|
+
|
114
|
+
Enspirit (https://enspirit.be) and Klaro App (https://klaro.cards) are both
|
115
|
+
actively using and contributing to the library.
|
data/bin/matt
ADDED
data/lib/matt.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'json'
|
4
|
+
require 'sequel'
|
5
|
+
require 'bmg'
|
6
|
+
require 'path'
|
7
|
+
require 'bmg/sequel'
|
8
|
+
module Matt
|
9
|
+
|
10
|
+
def env(which, default = nil)
|
11
|
+
val = ENV.has_key?(which) ? ENV[which] : default
|
12
|
+
val = val.strip if val.is_a?(String)
|
13
|
+
val
|
14
|
+
end
|
15
|
+
module_function :env
|
16
|
+
|
17
|
+
def alltime_predicate
|
18
|
+
Predicate.tautology
|
19
|
+
end
|
20
|
+
module_function :alltime_predicate
|
21
|
+
|
22
|
+
def yesterday_predicate
|
23
|
+
Predicate.gte(:at, Date.today - 1) & Predicate.lt(:at, Date.today)
|
24
|
+
end
|
25
|
+
module_function :yesterday_predicate
|
26
|
+
|
27
|
+
def today_predicate
|
28
|
+
Predicate.gte(:at, Date.today) & Predicate.lt(:at, Date.today + 1)
|
29
|
+
end
|
30
|
+
module_function :today_predicate
|
31
|
+
|
32
|
+
end # module Matt
|
33
|
+
require_relative 'matt/version'
|
34
|
+
require_relative 'matt/support'
|
35
|
+
require_relative 'matt/datasource'
|
36
|
+
require_relative 'matt/measure'
|
37
|
+
require_relative 'matt/exporter'
|
38
|
+
require_relative 'matt/configuration'
|
39
|
+
require_relative 'matt/command'
|
data/lib/matt/command.rb
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
#/
|
2
|
+
#/ Matt - A monitoring tool for data-driven business decisions
|
3
|
+
#/ vVERSION - (c) Enspirit SRL, 2021 and beyond
|
4
|
+
#/
|
5
|
+
#/ Usage: matt [-f folder] command [options...] [args...]
|
6
|
+
#/
|
7
|
+
#/ show measure Show the datapoints of a given measure
|
8
|
+
#/ export measure* Export all (or specific) measure(s) to
|
9
|
+
#/ ping datasource* Ping all (or specific) datasource(s)
|
10
|
+
#/ their default exporters
|
11
|
+
#/
|
12
|
+
#/ -f FOLDER Use the folder as entry point (`pwd` by default)
|
13
|
+
#/ --all-time Do not restrict measures shown/exported
|
14
|
+
#/ --today Only show/export measures for today
|
15
|
+
#/ --yesterday Only show/export measures for yesterday's (default)
|
16
|
+
#/ --to=exporter,... Override the default exporters
|
17
|
+
#/ --json Use json when displaying measures on console
|
18
|
+
#/ --csv Use csv when displaying measures on console (default)
|
19
|
+
#/ -h, --help Show this help message
|
20
|
+
#/ --version Show matt version
|
21
|
+
#/
|
22
|
+
#/ Examples:
|
23
|
+
#/ matt show --all-time --json account_creations
|
24
|
+
#/ matt export --today --to=sql,prometheus account_creations
|
25
|
+
#/
|
26
|
+
require 'optparse'
|
27
|
+
module Matt
|
28
|
+
class Command
|
29
|
+
include Support::Puts
|
30
|
+
|
31
|
+
attr_accessor :exitcode
|
32
|
+
attr_accessor :output_format
|
33
|
+
attr_accessor :to
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@output_format = :csv
|
37
|
+
@to = nil
|
38
|
+
yield(self) if block_given?
|
39
|
+
end
|
40
|
+
|
41
|
+
def on_configuration(&bl)
|
42
|
+
@on_configuration = bl
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def configuration
|
47
|
+
return @configuration if @configuration
|
48
|
+
self.configuration = Configuration.new(Path.pwd)
|
49
|
+
end
|
50
|
+
|
51
|
+
def configuration=(c)
|
52
|
+
@on_configuration.call(c) if @on_configuration
|
53
|
+
@configuration = c
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_configuration?
|
57
|
+
!!@configuration
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_argv(argv)
|
61
|
+
opt_parser.parse!(argv)
|
62
|
+
end
|
63
|
+
|
64
|
+
def call(argv)
|
65
|
+
ok = false
|
66
|
+
catch :abort do
|
67
|
+
parse_argv(argv)
|
68
|
+
_call(argv)
|
69
|
+
ok = true
|
70
|
+
end
|
71
|
+
self.exitcode = ok ? 0 : 1
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def _call(argv)
|
77
|
+
if argv.empty?
|
78
|
+
puts_out opt_parser
|
79
|
+
abort
|
80
|
+
end
|
81
|
+
|
82
|
+
meth = :"do_#{argv.first}"
|
83
|
+
if self.respond_to?(meth, true)
|
84
|
+
send(meth, argv[1..-1])
|
85
|
+
else
|
86
|
+
puts_err "No such command #{argv.first}"
|
87
|
+
abort
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def do_show(argv)
|
92
|
+
argv_count!(argv, 1)
|
93
|
+
m = measure_exists!(argv.first)
|
94
|
+
data = m.full_data.restrict(configuration.at_predicate)
|
95
|
+
case of = output_format
|
96
|
+
when :json
|
97
|
+
puts_out JSON.pretty_generate(data)
|
98
|
+
when :csv
|
99
|
+
puts_out data.to_csv(configuration.csv_options)
|
100
|
+
else
|
101
|
+
puts_err "Unknown format #{of}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def do_ping(argv)
|
106
|
+
which_ones = if argv.empty?
|
107
|
+
configuration.datasources.to_h.values
|
108
|
+
else
|
109
|
+
argv.map{|arg| datasource_exists!(arg) }
|
110
|
+
end
|
111
|
+
which_ones.each do |d|
|
112
|
+
d.ping
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def do_export(argv)
|
117
|
+
argv_count!(argv, 1)
|
118
|
+
m = measure_exists!(argv.first)
|
119
|
+
data = m.full_data.restrict(configuration.at_predicate)
|
120
|
+
(@to || m.exporters).each do |e|
|
121
|
+
exporter = exporter_exists!(e)
|
122
|
+
exporter.export(m, data)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def do_help(*args)
|
127
|
+
file = __FILE__
|
128
|
+
exec "grep ^#/<'#{file}'|cut -c4-|sed s/VERSION/#{Matt::VERSION}/g"
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
|
133
|
+
def opt_parser
|
134
|
+
OptionParser.new do |opts|
|
135
|
+
opts.banner = "Usage: matt [options] COMMAND [args]"
|
136
|
+
opts.on("-f FOLDER") do |folder|
|
137
|
+
p = Path(folder)
|
138
|
+
if has_configuration?
|
139
|
+
puts_err "-f must be used before other configuration options"
|
140
|
+
abort
|
141
|
+
elsif p.exists? && p.directory?
|
142
|
+
self.configuration = Configuration.new(p)
|
143
|
+
else
|
144
|
+
puts_err "No such folder: #{folder}"
|
145
|
+
abort
|
146
|
+
end
|
147
|
+
end
|
148
|
+
opts.on("--all-time") do
|
149
|
+
self.configuration.at_predicate = Matt.alltime_predicate
|
150
|
+
end
|
151
|
+
opts.on("--yesterday") do
|
152
|
+
self.configuration.at_predicate = Matt.yesterday_predicate
|
153
|
+
end
|
154
|
+
opts.on("--today") do
|
155
|
+
self.configuration.at_predicate = Matt.today_predicate
|
156
|
+
end
|
157
|
+
opts.on("--to=EXPORTERS") do |exporters|
|
158
|
+
@to = (@to || []) + exporters.split(/\s*,\s*/).map{|e|
|
159
|
+
exporter_exists!(e.to_sym).name
|
160
|
+
}
|
161
|
+
end
|
162
|
+
opts.on("--json") do
|
163
|
+
self.output_format = :json
|
164
|
+
end
|
165
|
+
opts.on("--csv") do
|
166
|
+
self.output_format = :csv
|
167
|
+
end
|
168
|
+
opts.on('--version', "Show version number") do
|
169
|
+
puts_out "Matt v#{VERSION} - (c) Enspirit SRL"
|
170
|
+
abort
|
171
|
+
end
|
172
|
+
opts.on("-h", "--help", "Prints this help") do
|
173
|
+
do_help
|
174
|
+
abort
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def argv_count!(argv, n)
|
180
|
+
return if argv.size == n
|
181
|
+
puts_err "#{n} arguments expected, got #{argv.size}"
|
182
|
+
abort
|
183
|
+
end
|
184
|
+
|
185
|
+
def measure_exists!(name)
|
186
|
+
m = configuration.measures.send(name.to_sym)
|
187
|
+
return m if m
|
188
|
+
puts_err "No such measure #{name}"
|
189
|
+
abort
|
190
|
+
end
|
191
|
+
|
192
|
+
def exporter_exists!(name)
|
193
|
+
m = configuration.exporters.send(name.to_sym)
|
194
|
+
return m if m
|
195
|
+
puts_err "No such exporter #{name}"
|
196
|
+
abort
|
197
|
+
end
|
198
|
+
|
199
|
+
def datasource_exists!(name)
|
200
|
+
d = configuration.datasources.send(name.to_sym)
|
201
|
+
return d if d
|
202
|
+
puts_err "No such datasource #{name}"
|
203
|
+
abort
|
204
|
+
end
|
205
|
+
|
206
|
+
def abort
|
207
|
+
throw :abort
|
208
|
+
end
|
209
|
+
|
210
|
+
end # class Command
|
211
|
+
end # module Matt
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Matt
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
def initialize(folder)
|
5
|
+
@folder = folder
|
6
|
+
@stdout = $stdout
|
7
|
+
@stderr = $stderr
|
8
|
+
@csv_options = {
|
9
|
+
:write_headers => true
|
10
|
+
}
|
11
|
+
@at_predicate = Matt.yesterday_predicate
|
12
|
+
yield(self) if block_given?
|
13
|
+
end
|
14
|
+
attr_accessor :folder
|
15
|
+
attr_accessor :stdout, :stderr
|
16
|
+
attr_accessor :csv_options
|
17
|
+
attr_accessor :at_predicate
|
18
|
+
|
19
|
+
def datasources_folder
|
20
|
+
folder/"datasources"
|
21
|
+
end
|
22
|
+
|
23
|
+
def datasources
|
24
|
+
@datasources ||= load_dynamic_objects(datasources_folder, Matt::Datasource)
|
25
|
+
end
|
26
|
+
alias :ds :datasources
|
27
|
+
|
28
|
+
def measures_folder
|
29
|
+
folder/"measures"
|
30
|
+
end
|
31
|
+
|
32
|
+
def measures
|
33
|
+
@measures ||= load_dynamic_objects(measures_folder, Matt::Measure)
|
34
|
+
end
|
35
|
+
alias :ms :measures
|
36
|
+
|
37
|
+
def exporters_folder
|
38
|
+
folder/"exporters"
|
39
|
+
end
|
40
|
+
|
41
|
+
def exporters
|
42
|
+
@exporters ||= load_dynamic_objects(exporters_folder, Matt::Exporter)
|
43
|
+
end
|
44
|
+
alias :es :exporters
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def load_dynamic_objects(folder, expected_type)
|
49
|
+
struct = OpenStruct.new
|
50
|
+
folder.glob("*.rb").each_with_object(struct) do |file,memo|
|
51
|
+
obj = load_dynamic_object(file)
|
52
|
+
if obj.nil?
|
53
|
+
raise "Wrong file #{file}: eval returned nil"
|
54
|
+
elsif !obj.is_a?(expected_type)
|
55
|
+
raise "Wrong file #{file}: must include #{expected_type}"
|
56
|
+
else
|
57
|
+
obj.configuration = self
|
58
|
+
obj.name = file.basename.rm_ext.to_s
|
59
|
+
memo.send("#{obj.name}=", obj)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_dynamic_object(file)
|
65
|
+
self.instance_eval(file.read, file.to_s)
|
66
|
+
end
|
67
|
+
|
68
|
+
end # class Configuration
|
69
|
+
end # module Matt
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Matt
|
2
|
+
module Datasource
|
3
|
+
include Support::Puts
|
4
|
+
|
5
|
+
attr_accessor :name
|
6
|
+
attr_accessor :configuration
|
7
|
+
|
8
|
+
def ping
|
9
|
+
raise NotImplementedError, "#{self} should implement `ping`"
|
10
|
+
end
|
11
|
+
|
12
|
+
end # module Datasource
|
13
|
+
end # module Matt
|
14
|
+
require_relative 'datasource/sql'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Matt
|
2
|
+
module Datasource
|
3
|
+
class Sql
|
4
|
+
include Matt::Datasource
|
5
|
+
|
6
|
+
def initialize(config)
|
7
|
+
@config = config
|
8
|
+
@sequel_db = ::Sequel.connect(config)
|
9
|
+
end
|
10
|
+
attr_reader :config, :sequel_db
|
11
|
+
|
12
|
+
def ping
|
13
|
+
sequel_db.test_connection
|
14
|
+
puts "#{self} -- Ok."
|
15
|
+
rescue => ex
|
16
|
+
puts_err "#{self} -- Ko: #{ex.message}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
if config.is_a?(Hash)
|
21
|
+
"#{config[:host]}:#{config[:port]}/#{config[:database]}"
|
22
|
+
else
|
23
|
+
config.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def sequel(*args)
|
28
|
+
Bmg.sequel(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Matt
|
2
|
+
module Exporter
|
3
|
+
class Sql
|
4
|
+
include Matt::Exporter
|
5
|
+
|
6
|
+
def initialize(arg)
|
7
|
+
case @config = arg
|
8
|
+
when String
|
9
|
+
when Hash
|
10
|
+
when Sequel::Database
|
11
|
+
when Matt::Datasource::Sql
|
12
|
+
else
|
13
|
+
raise ArgumentError, "Unable to use `#{arg}` to create an Sql exporter"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
attr_reader :config
|
17
|
+
|
18
|
+
def sequel_db
|
19
|
+
@sequel_db ||= case config
|
20
|
+
when Sequel::Database then config
|
21
|
+
when Matt::Datasource::Sql then config.sequel_db
|
22
|
+
else Sequel.connect(config)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def export(measure, data)
|
27
|
+
now = Time.now
|
28
|
+
data = data.constants(:created_at => now)
|
29
|
+
table = ensure_table!(measure)
|
30
|
+
remove_old_data!(table, data)
|
31
|
+
insert_new_data!(table, data)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def remove_old_data!(table, data)
|
37
|
+
dates = data.project([:at]).map{|t| t[:at] }
|
38
|
+
table.where(:at => dates).delete
|
39
|
+
end
|
40
|
+
|
41
|
+
def insert_new_data!(table, data)
|
42
|
+
table.multi_insert(data.to_a)
|
43
|
+
end
|
44
|
+
|
45
|
+
def ensure_table!(measure)
|
46
|
+
tb_name = measure.name.to_sym
|
47
|
+
fields = [:at] + measure.dimensions.keys + measure.metrics.keys
|
48
|
+
if sequel_db.table_exists?(tb_name)
|
49
|
+
align_table!(tb_name, measure, fields)
|
50
|
+
else
|
51
|
+
create_table!(tb_name, measure, fields)
|
52
|
+
end
|
53
|
+
sequel_db[tb_name]
|
54
|
+
end
|
55
|
+
|
56
|
+
def align_table!(tb_name, measure, fields)
|
57
|
+
schema = sequel_db.schema(tb_name)
|
58
|
+
existing = schema.map{|s| s.first } - [:created_at]
|
59
|
+
missing = fields - existing
|
60
|
+
missing = measure.dimensions.merge(measure.metrics).select{|x|
|
61
|
+
missing.include?(x)
|
62
|
+
}
|
63
|
+
outdated = existing - fields
|
64
|
+
return if missing.empty? && outdated.empty?
|
65
|
+
sequel_db.alter_table(tb_name) do
|
66
|
+
outdated.each do |out|
|
67
|
+
drop_column(out)
|
68
|
+
end
|
69
|
+
missing.each_pair do |mis, type|
|
70
|
+
add_column(mis, type, null: true)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_table!(tb_name, measure, fields)
|
76
|
+
sequel_db.create_table(tb_name) do
|
77
|
+
column(:at, Date, :null => false)
|
78
|
+
column(:created_at, :timestamp, :null => false)
|
79
|
+
measure.dimensions.each do |name,type|
|
80
|
+
column(name, type, :null => true)
|
81
|
+
end
|
82
|
+
measure.metrics.each do |name,type|
|
83
|
+
column(name, type, :null => true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end # class Sql
|
89
|
+
end # module Exporter
|
90
|
+
end # module Matt
|
data/lib/matt/measure.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Matt
|
2
|
+
module Measure
|
3
|
+
include Support::Puts
|
4
|
+
|
5
|
+
attr_accessor :name
|
6
|
+
attr_accessor :configuration
|
7
|
+
|
8
|
+
def dimensions
|
9
|
+
{}
|
10
|
+
end
|
11
|
+
|
12
|
+
def exporters
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
|
16
|
+
def metrics
|
17
|
+
raise NotImplementedError, "#{self} must implement `metrics`"
|
18
|
+
end
|
19
|
+
|
20
|
+
def ds
|
21
|
+
configuration.datasources
|
22
|
+
end
|
23
|
+
|
24
|
+
end # module Measure
|
25
|
+
end # module Matt
|
data/lib/matt/support.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'support/puts'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Matt
|
2
|
+
module Support
|
3
|
+
module Puts
|
4
|
+
|
5
|
+
def stdout
|
6
|
+
configuration.stdout
|
7
|
+
end
|
8
|
+
|
9
|
+
def stderr
|
10
|
+
configuration.stderr
|
11
|
+
end
|
12
|
+
|
13
|
+
def puts_out(*args, &bl)
|
14
|
+
return $stdout.puts(*args, &bl) unless configuration
|
15
|
+
configuration.stdout.puts(*args, &bl)
|
16
|
+
end
|
17
|
+
alias :puts :puts_out
|
18
|
+
|
19
|
+
def puts_err(*args, &bl)
|
20
|
+
return $stderr.puts(*args, &bl) unless configuration
|
21
|
+
configuration.stderr.puts(*args, &bl)
|
22
|
+
end
|
23
|
+
|
24
|
+
end # module Puts
|
25
|
+
end # module Support
|
26
|
+
end # module Matt
|
data/lib/matt/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: matt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bernard Lambeau
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-06-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: path
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: bmg
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.18.5
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0.19'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.18.5
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0.19'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: sequel
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '5.0'
|
60
|
+
- - "<"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '6.19'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '5.0'
|
70
|
+
- - "<"
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '6.19'
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: rake
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '13.0'
|
80
|
+
- - "<"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '14.0'
|
83
|
+
type: :development
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '13.0'
|
90
|
+
- - "<"
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '14.0'
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: rspec
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '3.10'
|
100
|
+
- - "<"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '4.0'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3.10'
|
110
|
+
- - "<"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '4.0'
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: sqlite3
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: 1.4.2
|
120
|
+
- - "<"
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '2.0'
|
123
|
+
type: :development
|
124
|
+
prerelease: false
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: 1.4.2
|
130
|
+
- - "<"
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '2.0'
|
133
|
+
description: Matt help monitoring data points for better business decision.
|
134
|
+
email: blambeau@gmail.com
|
135
|
+
executables:
|
136
|
+
- matt
|
137
|
+
extensions: []
|
138
|
+
extra_rdoc_files: []
|
139
|
+
files:
|
140
|
+
- Gemfile
|
141
|
+
- README.md
|
142
|
+
- bin/matt
|
143
|
+
- lib/matt.rb
|
144
|
+
- lib/matt/command.rb
|
145
|
+
- lib/matt/configuration.rb
|
146
|
+
- lib/matt/datasource.rb
|
147
|
+
- lib/matt/datasource/sql.rb
|
148
|
+
- lib/matt/exporter.rb
|
149
|
+
- lib/matt/exporter/sql.rb
|
150
|
+
- lib/matt/measure.rb
|
151
|
+
- lib/matt/support.rb
|
152
|
+
- lib/matt/support/puts.rb
|
153
|
+
- lib/matt/version.rb
|
154
|
+
homepage: http://github.com/enspirit/matt
|
155
|
+
licenses:
|
156
|
+
- MIT
|
157
|
+
metadata: {}
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
requirements: []
|
173
|
+
rubygems_version: 3.2.15
|
174
|
+
signing_key:
|
175
|
+
specification_version: 4
|
176
|
+
summary: Matt help monitoring data points for better business decision.
|
177
|
+
test_files: []
|