evoc 3.5.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 +15 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/Makefile +4 -0
- data/README.md +61 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/evoc +3 -0
- data/bin/setup +7 -0
- data/evoc.gemspec +30 -0
- data/lib/evoc/algorithm.rb +147 -0
- data/lib/evoc/algorithms/top_k.rb +86 -0
- data/lib/evoc/analyze.rb +395 -0
- data/lib/evoc/array.rb +43 -0
- data/lib/evoc/evaluate.rb +109 -0
- data/lib/evoc/exceptions/aggregation_error.rb +6 -0
- data/lib/evoc/exceptions/expectedoutcome_nil_or_empty.rb +6 -0
- data/lib/evoc/exceptions/measure_calculation_error.rb +6 -0
- data/lib/evoc/exceptions/no_changed_items_in_changes.rb +6 -0
- data/lib/evoc/exceptions/no_changes_in_json_object.rb +6 -0
- data/lib/evoc/exceptions/no_date_in_json_object.rb +6 -0
- data/lib/evoc/exceptions/no_result.rb +6 -0
- data/lib/evoc/exceptions/non_finite.rb +8 -0
- data/lib/evoc/exceptions/non_numeric.rb +8 -0
- data/lib/evoc/exceptions/not_a_query.rb +6 -0
- data/lib/evoc/exceptions/not_a_result.rb +6 -0
- data/lib/evoc/exceptions/not_a_transaction.rb +6 -0
- data/lib/evoc/exceptions/not_initialized.rb +6 -0
- data/lib/evoc/exceptions/only_nil_in_changes.rb +6 -0
- data/lib/evoc/exceptions/query_nil_or_empty.rb +6 -0
- data/lib/evoc/exceptions/unable_to_convert_json_to_tx.rb +6 -0
- data/lib/evoc/experiment.rb +239 -0
- data/lib/evoc/hash.rb +56 -0
- data/lib/evoc/history_store.rb +53 -0
- data/lib/evoc/hyper_rule.rb +53 -0
- data/lib/evoc/interestingness_measure.rb +77 -0
- data/lib/evoc/interestingness_measure_aggregator.rb +147 -0
- data/lib/evoc/interestingness_measures.rb +882 -0
- data/lib/evoc/logger.rb +34 -0
- data/lib/evoc/memory_profiler.rb +43 -0
- data/lib/evoc/recommendation_cache.rb +152 -0
- data/lib/evoc/rule.rb +32 -0
- data/lib/evoc/rule_store.rb +340 -0
- data/lib/evoc/scenario.rb +303 -0
- data/lib/evoc/svd.rb +124 -0
- data/lib/evoc/tx.rb +34 -0
- data/lib/evoc/tx_store.rb +379 -0
- data/lib/evoc/version.rb +3 -0
- data/lib/evoc.rb +4 -0
- data/lib/evoc_cli/analyze.rb +198 -0
- data/lib/evoc_cli/cli_helper.rb +1 -0
- data/lib/evoc_cli/experiment.rb +78 -0
- data/lib/evoc_cli/info.rb +22 -0
- data/lib/evoc_cli/main.rb +29 -0
- data/lib/evoc_cli/util.rb +36 -0
- data/lib/evoc_helper.rb +40 -0
- data/mem_profiler/Gemfile.lock +39 -0
- data/mem_profiler/README.md +126 -0
- data/mem_profiler/createdb.rb +4 -0
- data/mem_profiler/db.rb +82 -0
- data/mem_profiler/gemfile +6 -0
- data/mem_profiler/gencsv.rb +64 -0
- data/mem_profiler/genimport.sh +8 -0
- data/mem_profiler/graph.rb +91 -0
- metadata +251 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'cli_helper'
|
2
|
+
|
3
|
+
module EvocCLI
|
4
|
+
class Main < Thor
|
5
|
+
map %w[--version -v] => :__print_version
|
6
|
+
|
7
|
+
##
|
8
|
+
# default thor behavior is to return exit 0 on errors (i.e., success..)
|
9
|
+
# by having exit_on_failure return true, exit(1) is returned instead
|
10
|
+
def self.exit_on_failure?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "--version, -v", "print the version"
|
15
|
+
def __print_version
|
16
|
+
puts Evoc::VERSION
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "info SUBCOMMAND [options]", "Print info on implemented measures etc"
|
20
|
+
subcommand "info", Info
|
21
|
+
|
22
|
+
desc "analyze SUBCOMMAND [options]", "Various tools for analysis on version histories"
|
23
|
+
subcommand "analyze", Analyze
|
24
|
+
|
25
|
+
desc "experiment SUBCOMMAND", "Various tools for setting up and running experiments in the context of targeted association rule mining"
|
26
|
+
subcommand "experiment", Experiment
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'cli_helper'
|
2
|
+
|
3
|
+
module EvocCLI
|
4
|
+
class Util < Thor
|
5
|
+
|
6
|
+
method_option :exclude_columns, :aliases => '-x', :type => :array, default: [], :desc => "Keys/Columns to exclude from the csv"
|
7
|
+
desc "json2csv [OPTIONS] JSON","Convert a json file to csv"
|
8
|
+
def json2csv(path)
|
9
|
+
json = nil
|
10
|
+
if File.extname(path) == '.gz'
|
11
|
+
Zlib::GzipReader.open(path) {|gz|
|
12
|
+
json = gz.read
|
13
|
+
}
|
14
|
+
else
|
15
|
+
json = File.read(path,external_encoding: 'iso-8859-1',internal_encoding: 'utf-8')
|
16
|
+
end
|
17
|
+
header = []
|
18
|
+
header_not_written = true
|
19
|
+
JSON.parse(json).each do |json_object|
|
20
|
+
if header_not_written
|
21
|
+
header = json_object.keys
|
22
|
+
options[:exclude_columns].each {|c| header.delete(c)}
|
23
|
+
CSV {|row| row << header}
|
24
|
+
header_not_written = false
|
25
|
+
end
|
26
|
+
# remove unwanted keys
|
27
|
+
options[:exclude_columns].each {|c| json_object.delete(c)}
|
28
|
+
if json_object.keys == header
|
29
|
+
CSV {|row| row << json_object.values.map {|v| v.is_a?(Array) ? v.join(",") : v }}
|
30
|
+
else
|
31
|
+
raise ArgumentError, "CSV header (made from first json object) didn't match with the keys in the current json object, the keys were: #{json_object.keys}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/evoc_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# define some helper functions to reason about the runtime environment
|
2
|
+
module Evoc
|
3
|
+
module Env
|
4
|
+
def self.load(gem,failure_msg="")
|
5
|
+
begin
|
6
|
+
require gem
|
7
|
+
self.const_set(gem.upcase, true)
|
8
|
+
true
|
9
|
+
rescue LoadError
|
10
|
+
$stderr.puts "[#{gem} gem not installed] #{failure_msg}"
|
11
|
+
self.const_set(gem.upcase, false)
|
12
|
+
false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'pp'
|
19
|
+
require 'json'
|
20
|
+
require 'yaml'
|
21
|
+
require 'thor'
|
22
|
+
require 'time'
|
23
|
+
require 'digest'
|
24
|
+
require 'csv'
|
25
|
+
require 'time_difference'
|
26
|
+
require 'ruby-progressbar'
|
27
|
+
require 'logger'
|
28
|
+
require 'ruby-prof'
|
29
|
+
require 'zip'
|
30
|
+
require 'zip/filesystem'
|
31
|
+
require 'set'
|
32
|
+
require 'algorithms' # various efficient data structures
|
33
|
+
require 'mathn' # enhances the Rational (and others) number type
|
34
|
+
Evoc::Env.load('google_hash',"please install to improve performance")
|
35
|
+
#Evoc::Env.load('nmatrix')
|
36
|
+
#Evoc::Env.load('nmatrix/lapacke')
|
37
|
+
|
38
|
+
require 'require_all'
|
39
|
+
require_rel '/**/*.rb'
|
40
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (4.2.7.1)
|
5
|
+
activesupport (= 4.2.7.1)
|
6
|
+
builder (~> 3.1)
|
7
|
+
activerecord (4.2.7.1)
|
8
|
+
activemodel (= 4.2.7.1)
|
9
|
+
activesupport (= 4.2.7.1)
|
10
|
+
arel (~> 6.0)
|
11
|
+
activesupport (4.2.7.1)
|
12
|
+
i18n (~> 0.7)
|
13
|
+
json (~> 1.7, >= 1.7.7)
|
14
|
+
minitest (~> 5.1)
|
15
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
16
|
+
tzinfo (~> 1.1)
|
17
|
+
arel (6.0.3)
|
18
|
+
builder (3.2.2)
|
19
|
+
gnuplot (2.6.2)
|
20
|
+
i18n (0.7.0)
|
21
|
+
json (1.8.3)
|
22
|
+
minitest (5.9.0)
|
23
|
+
pg (0.18.4)
|
24
|
+
ruby-progressbar (1.7.5)
|
25
|
+
thread_safe (0.3.5)
|
26
|
+
tzinfo (1.2.2)
|
27
|
+
thread_safe (~> 0.1)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
activerecord (~> 4.2.5)
|
34
|
+
gnuplot (~> 2.6.2)
|
35
|
+
pg (~> 0.18.4)
|
36
|
+
ruby-progressbar (~> 1.7.5)
|
37
|
+
|
38
|
+
BUNDLED WITH
|
39
|
+
1.12.5
|
@@ -0,0 +1,126 @@
|
|
1
|
+
Finding a Ruby memory leak using a time analysis
|
2
|
+
================================================
|
3
|
+
|
4
|
+
When developing a program in [Ruby](http://ruby-lang.org), you may sometimes encounter a memory leak.
|
5
|
+
For a while now, Ruby has a facility to gather information about what objects are laying around:
|
6
|
+
[ObjectSpace](http://ruby-doc.org/core/ObjectSpace.html).
|
7
|
+
|
8
|
+
There are several approaches one can take to debug a leak. This discusses a time-based approach, where
|
9
|
+
a full memory dump is generated every, say, 5 minutes, during a time that the memory leak is showing up.
|
10
|
+
Afterwards, one can look at all the objects, and find out which ones are staying around, causing the
|
11
|
+
memory leak.
|
12
|
+
|
13
|
+
|
14
|
+
Gather
|
15
|
+
------
|
16
|
+
|
17
|
+
Setup your Ruby application to dump all objects to a file. If you have an event loop, something like this would work:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'objspace'
|
21
|
+
|
22
|
+
def heap_dump
|
23
|
+
GC.start
|
24
|
+
|
25
|
+
i = Time.now.strftime('%s')
|
26
|
+
|
27
|
+
open("/tmp/ruby-heap-#{i}.dump", "w") do |io|
|
28
|
+
ObjectSpace.dump_all(output: io)
|
29
|
+
end
|
30
|
+
|
31
|
+
# On Heroku you'll need to push it elsewhere, like S3
|
32
|
+
#s3 = AWS::S3.new(access_key_id: ENV['S3_ACCESS_KEY'], secret_access_key: ENV['S3_SECRET_KEY'])
|
33
|
+
#bucket = s3.buckets['qm-import-export']
|
34
|
+
#obj = bucket.objects["ruby-heap-#{i}.jsonl"]
|
35
|
+
#obj.write(IO.binread(path))
|
36
|
+
end
|
37
|
+
|
38
|
+
ObjectSpace.trace_object_allocations_start
|
39
|
+
mainloop do
|
40
|
+
# assuming your mainloop does the work, and calls this block every 5 minutes
|
41
|
+
heap_dump
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Or, if you're having a Rails app, do this in a controller that you visit every 5 minutes
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
# app/controllers/heap_dumps_controller.rb
|
49
|
+
class HeapDumpsController < ActionController::Metal
|
50
|
+
|
51
|
+
def heap_dump
|
52
|
+
if ENV['HEAP_DUMP'] == '1' && params[:token].to_s == ENV['HEAP_DUMP_TOKEN']
|
53
|
+
heap_dump
|
54
|
+
self.response_body = 'Dumped heap'
|
55
|
+
else
|
56
|
+
self.status = 401
|
57
|
+
self.response_body = 'Invalid token'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# add to config/routes.rb
|
63
|
+
get "/heap_dump", to: HeapDumpsController.action(:heap_dump)
|
64
|
+
|
65
|
+
# config/initializers/heap_dump_tracing.rb
|
66
|
+
if ENV['HEAP_DUMP'] == 1
|
67
|
+
require 'objspace'
|
68
|
+
ObjectSpace.trace_object_allocations_start
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
|
73
|
+
Install
|
74
|
+
-------
|
75
|
+
|
76
|
+
- Having [Ruby](http://ruby-lang.org/), install the dependencies with `bundle install`.
|
77
|
+
- Having [PostgreSQL](http://postgresql.org/), create the database with `createdb mem_analysis`.
|
78
|
+
- When getting dumps from Amazon S3, [s3cmd](https://github.com/s3tools/s3cmd) may come in handy.
|
79
|
+
|
80
|
+
|
81
|
+
Import
|
82
|
+
------
|
83
|
+
|
84
|
+
If stored on S3, get the dump list. Update the bucket and date in the grep command to reflect your case. This stores filenames and dates in _index.txt_.
|
85
|
+
|
86
|
+
S3_URL=s3://qm-import-export/
|
87
|
+
s3cmd ls $S3_URL | grep '^2015-11-23' | sed 's/[0-9]*\+\s\+s3:.*\///' >index.txt
|
88
|
+
|
89
|
+
Then download them:
|
90
|
+
|
91
|
+
for file in `cat index.txt | awk '{print $3}'`; do s3cmd get $S3_URL/$file $file; done
|
92
|
+
|
93
|
+
Initialize the database:
|
94
|
+
|
95
|
+
bundle exec ruby createdb.rb
|
96
|
+
|
97
|
+
Because importing can take quite a while, this is split into two steps: converting each file to SQL, and loading all into the database:
|
98
|
+
|
99
|
+
bundle exec ruby gencsv.rb
|
100
|
+
sh genimport.sh | psql mem_analysis
|
101
|
+
|
102
|
+
|
103
|
+
Analyse
|
104
|
+
-------
|
105
|
+
|
106
|
+
Now that the database is loaded, we're ready to gather information.
|
107
|
+
To find out what is causing a memory leak, we can look at graphs plotting memory usage over time in different dimensions.
|
108
|
+
This is done by `graph.rb`. Let's start with the object type.
|
109
|
+
|
110
|
+
bundle exec ruby graph.rb type-mem
|
111
|
+
|
112
|
+
This will create the file _graph-type-mem.png_ showing the total size of objects by type. If there's one thing leaking,
|
113
|
+
you'll probably have a number of somewhat flat lines, and one with a positive slope, which is the culprit.
|
114
|
+
|
115
|
+
Then create a similar graph for that object type only, and plot lines by file, for example. This gives one an idea in which
|
116
|
+
gem the leaking objects may be created. If it's a string, run
|
117
|
+
|
118
|
+
bundle exec ruby graph.rb string-mem
|
119
|
+
|
120
|
+
If it's something else, edit _graph.rb_ and expand the `case`-block. In this way you may be able to zoom in on the cause.
|
121
|
+
|
122
|
+
|
123
|
+
Sample
|
124
|
+
------
|
125
|
+
|
126
|
+

|
data/mem_profiler/db.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
ActiveRecord::Base.establish_connection({adapter: 'postgresql', database: 'mem_analysis'})
|
5
|
+
|
6
|
+
def connection
|
7
|
+
ActiveRecord::Base.connection
|
8
|
+
end
|
9
|
+
|
10
|
+
class SpaceObject < ActiveRecord::Base
|
11
|
+
self.inheritance_column = 'zoink' # use type as ordinary column (not STI)
|
12
|
+
has_many :references, class_name: 'SpaceObjectReference', foreign_key: 'from_id', inverse_of: 'from', dependent: :destroy
|
13
|
+
has_one :default, class_name: 'SpaceObject', foreign_key: 'default', primary_key: 'address'
|
14
|
+
end
|
15
|
+
|
16
|
+
class SpaceObjectReference < ActiveRecord::Base
|
17
|
+
belongs_to :from, class_name: 'SpaceObject', required: true, inverse_of: 'references'
|
18
|
+
belongs_to :to, class_name: 'SpaceObject', foreign_key: 'to_address', primary_key: 'address'
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def init_database(c = connection)
|
23
|
+
c.tables.each {|t| c.drop_table(t) }
|
24
|
+
c.create_table 'space_objects' do |t|
|
25
|
+
t.datetime :time
|
26
|
+
t.string :type
|
27
|
+
t.string :node_type
|
28
|
+
t.string :root
|
29
|
+
t.string :address
|
30
|
+
t.text :value
|
31
|
+
t.string :klass
|
32
|
+
t.string :name
|
33
|
+
t.string :struct
|
34
|
+
t.string :file
|
35
|
+
t.string :line
|
36
|
+
t.string :method
|
37
|
+
t.integer :generation
|
38
|
+
t.integer :size
|
39
|
+
t.integer :length
|
40
|
+
t.integer :memsize
|
41
|
+
t.integer :bytesize
|
42
|
+
t.integer :capacity
|
43
|
+
t.integer :ivars
|
44
|
+
t.integer :fd
|
45
|
+
t.string :encoding
|
46
|
+
t.string :default_address
|
47
|
+
t.boolean :freezed
|
48
|
+
t.boolean :fstring
|
49
|
+
t.boolean :embedded
|
50
|
+
t.boolean :shared
|
51
|
+
t.boolean :flag_wb_protected
|
52
|
+
t.boolean :flag_old
|
53
|
+
t.boolean :flag_long_lived
|
54
|
+
t.boolean :flag_marking
|
55
|
+
t.boolean :flag_marked
|
56
|
+
end
|
57
|
+
c.create_table 'space_object_references' do |t|
|
58
|
+
t.integer :from_id, null: false
|
59
|
+
t.string :to_address, null: false
|
60
|
+
end
|
61
|
+
restore_indexes
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def remove_indexes(c = connection)
|
66
|
+
c.indexes('space_objects').each {|i| connection.remove_index('space_objects', name: i.name) }
|
67
|
+
c.indexes('space_objects_references').each {|i| connection.remove_index('space_objects_references', name: i.name) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def restore_indexes(c = connection)
|
71
|
+
c.change_table 'space_objects' do |t|
|
72
|
+
t.index :time
|
73
|
+
t.index :address
|
74
|
+
t.index :type
|
75
|
+
t.index [:klass, :method]
|
76
|
+
t.index [:file, :line]
|
77
|
+
t.index :size
|
78
|
+
t.index :memsize
|
79
|
+
end
|
80
|
+
c.execute('VACUUM ANALYZE')
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'ruby-progressbar'
|
3
|
+
require 'json'
|
4
|
+
require 'csv'
|
5
|
+
|
6
|
+
def parse_dump(filename, &block)
|
7
|
+
lines = open(filename).readlines
|
8
|
+
lines.each do |line|
|
9
|
+
block.call JSON.parse(line), lines.count
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_index(filename, &block)
|
14
|
+
open(filename).each do |line|
|
15
|
+
dumpname,time = line.split(',').map(&:chomp)
|
16
|
+
block.call dumpname, time
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
FIELDS = %w(time type node_type root address value klass name struct file line method generation size length memsize bytesize capacity ivars fd encoding default_address freezed fstring embedded shared flag_wb_protected flag_old flag_long_lived flag_marking flag_marked)
|
21
|
+
REF_FIELDS = %w(id from_id to_address)
|
22
|
+
|
23
|
+
if ARGV[0].empty?
|
24
|
+
STDERR.puts "You must provide the index file"
|
25
|
+
exit
|
26
|
+
else
|
27
|
+
id = 1
|
28
|
+
ref_id = 1
|
29
|
+
parse_index(ARGV[0]) do |file, time|
|
30
|
+
|
31
|
+
next if ARGV.any? && !ARGV.include?(file)
|
32
|
+
|
33
|
+
progressbar = ProgressBar.create(title: file, format: "%t |%B| %c/%C %E", throttle_rate: 0.5)
|
34
|
+
CSV.open(file.gsub(/.jsonl$/i, '') + '.csv', 'w') do |csv|
|
35
|
+
csv << FIELDS
|
36
|
+
CSV.open(file.gsub(/.jsonl$/i, '') + '.refs.csv', 'w') do |ref_csv|
|
37
|
+
ref_csv << REF_FIELDS
|
38
|
+
parse_dump(file) do |data, count|
|
39
|
+
progressbar.total = count
|
40
|
+
|
41
|
+
data['value'] = data['value'].gsub(/[^[:print:]]/, '.') if data['value'] # allow string database column
|
42
|
+
data['klass'] = data.delete('class') if data['class'] # avoid error
|
43
|
+
data['freezed'] = data.delete('frozen') if data['frozen'] # idem
|
44
|
+
data['default_address'] = data.delete('default') if data['default'] # consistency
|
45
|
+
data['time'] = time
|
46
|
+
data['id'] = id
|
47
|
+
(data.delete('flags') || {}).each {|k, v| data["flag_#{k}"] = v }
|
48
|
+
data['default_address'] = data.delete('default') if data['default']
|
49
|
+
refs = data.delete('references') || []
|
50
|
+
|
51
|
+
csv << FIELDS.map {|f| data[f]}
|
52
|
+
refs.each do |ref|
|
53
|
+
ref_csv << [ref_id, id, ref]
|
54
|
+
ref_id += 1
|
55
|
+
end
|
56
|
+
|
57
|
+
id += 1
|
58
|
+
progressbar.increment
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'date'
|
3
|
+
require 'yaml'
|
4
|
+
require 'gnuplot'
|
5
|
+
require_relative 'db'
|
6
|
+
|
7
|
+
|
8
|
+
### Parse arguments
|
9
|
+
|
10
|
+
type = ARGV[0]
|
11
|
+
type == 'type' and type = 'type-mem'
|
12
|
+
|
13
|
+
case type
|
14
|
+
when 'type-count'
|
15
|
+
ylabel = 'count'
|
16
|
+
query, ycolumn, group = nil, 'COUNT(id)', :type
|
17
|
+
key_pos = 'left top'
|
18
|
+
when 'type-mem'
|
19
|
+
query, ycolumn, group = nil, 'SUM(memsize)', :type
|
20
|
+
ylabel, yscale = 'memsize [MB]', 1024*1024
|
21
|
+
key_pos = 'left top'
|
22
|
+
when 'string-count'
|
23
|
+
ylabel = 'count'
|
24
|
+
query, ycolumn, group = {type: 'STRING'}, 'COUNT(id)', :file
|
25
|
+
when 'string-mem'
|
26
|
+
query, ycolumn, group = {type: 'STRING'}, 'SUM(memsize)', :file
|
27
|
+
ylabel, yscale = 'memsize [MB]', 1024*1024
|
28
|
+
when 'data-count'
|
29
|
+
ylabel = 'count'
|
30
|
+
query, ycolumn, group = {type: 'DATA'}, 'COUNT(id)', :file
|
31
|
+
when 'data-mem'
|
32
|
+
query, ycolumn, group = {type: 'DATA'}, 'SUM(memsize)', :file
|
33
|
+
ylabel, yscale = 'memsize [MB]', 1024*1024
|
34
|
+
else
|
35
|
+
STDERR.puts "Usage: graph <type>"
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
xoffset = 60*60 # GMT+1
|
40
|
+
graph_basename = File.dirname(File.expand_path(__FILE__)) + '/graph-' + type
|
41
|
+
|
42
|
+
|
43
|
+
### Read cache or execute query
|
44
|
+
|
45
|
+
if File.exists?(graph_basename + '.yml')
|
46
|
+
data = YAML.load(File.read(graph_basename + '.yml'))
|
47
|
+
else
|
48
|
+
scope = SpaceObject
|
49
|
+
scope = scope.where(**query) if query
|
50
|
+
scope = scope.order(ycolumn + ' DESC NULLS LAST')
|
51
|
+
scope = scope.group(:time, group)
|
52
|
+
data = scope.limit(500).pluck(group, :time, ycolumn)
|
53
|
+
File.open(graph_basename + '.yml', 'w') do |f|
|
54
|
+
f.write(data.to_yaml)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
### Then plot
|
60
|
+
|
61
|
+
Gnuplot.open(persist: true) do |gp|
|
62
|
+
Gnuplot::Plot.new(gp) do |plot|
|
63
|
+
plot.terminal 'png large'
|
64
|
+
plot.output graph_basename + '.png'
|
65
|
+
|
66
|
+
plot.xdata :time
|
67
|
+
plot.timefmt '"%s"'
|
68
|
+
plot.format 'x "%H:%M"'
|
69
|
+
|
70
|
+
plot.xlabel "time"
|
71
|
+
plot.ylabel ylabel
|
72
|
+
plot.key key_pos if key_pos
|
73
|
+
|
74
|
+
grouped_data = data.group_by(&:first)
|
75
|
+
keys = grouped_data.keys.sort_by {|key| -grouped_data[key].reduce(0) {|sum,d| sum + (d[2]||0) } }
|
76
|
+
keys[0,10].each do |key|
|
77
|
+
data = grouped_data[key]
|
78
|
+
data.sort_by!{|d| d[1] }
|
79
|
+
x = data.map{|d| d[1].to_i + (xoffset||0) }
|
80
|
+
y = data.map{|d| d[2] }
|
81
|
+
y = data.map{|d| (d[2]||0) / (yscale||1) }
|
82
|
+
plot.data << Gnuplot::DataSet.new( [x, y] ) do |ds|
|
83
|
+
ds.using = '1:2'
|
84
|
+
ds.with = "linespoints"
|
85
|
+
ds.title = key || '(empty)'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|