dstat_plot 0.6.2

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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/dstat-plot +4 -0
  3. data/lib/dstat_plot.rb +328 -0
  4. metadata +60 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2bb90a5fad4eb9f82a3e6186125642b257e0b08b
4
+ data.tar.gz: 6f1bca5b9ce0e72119d2ba23ec6201f0d438d472
5
+ SHA512:
6
+ metadata.gz: c7b09179a626d3228c2f6e6750450b30ee7b190ce32b1b19ea89765ac85c0d6b336f68a9ccd35616cccff6837f4bc0621f21f7506eee69f6a9aaaac56892bf24
7
+ data.tar.gz: 4b59a65a81082ca5516b765c020fb806c9760cee8f65bef4e092c1eb1330adb3c96ec4b75e74050711ceabb8843d549e932b572d59cd8142b6594443eb568946
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'dstat_plot'
4
+ DstatPlot.run
@@ -0,0 +1,328 @@
1
+ #! /usr/bin/ruby
2
+
3
+ require 'gnuplot'
4
+ require 'csv'
5
+ require 'optparse'
6
+
7
+ """
8
+ dstat_plot
9
+ plots csv data generated by dstat
10
+ """
11
+
12
+ $verbose = false
13
+ Y_DEFAULT = 105.0
14
+
15
+ def plot(dataset_container, category, field, dry, filename)
16
+ Gnuplot.open do |gp|
17
+ Gnuplot::Plot.new(gp) do |plot|
18
+ plot.title dataset_container[:plot_title].gsub('_', '\\\\\\\\_')
19
+ plot.xlabel "Time in seconds"
20
+ plot.ylabel "#{category}: #{field}"
21
+ plot.yrange "[0:#{dataset_container[:y_max] * 1.05}]"
22
+ if dataset_container[:autoscale] then plot.set "autoscale" end
23
+ plot.key "out vert right top"
24
+
25
+ unless dry
26
+ format = filename.split('.')[-1]
27
+ plot.terminal format + ' size 1600,800 enhanced font "Helvetica,11"'
28
+ plot.output filename
29
+ puts "Saving plot to '#{filename}'"
30
+ end
31
+
32
+ plot.data = dataset_container[:datasets]
33
+ end
34
+ end
35
+ end
36
+
37
+ def generate_filename(output, column, category, field, target_dir)
38
+ if output.nil? || File.directory?(output) # if an output file is not explicitly stated or if it's a directory
39
+ # generate filename
40
+ if column
41
+ generated_filename = "dstat-column#{column}.png"
42
+ else
43
+ generated_filename = "#{category}-#{field}.png".sub("/", "_")
44
+ end
45
+
46
+ # add directory portion
47
+ if output.nil?
48
+ filename = File.join(target_dir, generated_filename)
49
+ elsif File.directory?(output)
50
+ filename = File.join(output, generated_filename)
51
+ end
52
+ else # specific path+file is given so just use that
53
+ filename = output
54
+ end
55
+ end
56
+
57
+ # Calculate the average of groups of values from data
58
+ # Params:
59
+ # +data:: Array containing the data
60
+ # +slice_size:: number of values each group of data should contain
61
+ def average(data, slice_size)
62
+ reduced_data = []
63
+ data.each_slice(slice_size) do |slice|
64
+ reduced_data.push(slice.reduce(:+) / slice.size)
65
+ end
66
+ reduced_data
67
+ end
68
+
69
+ # Preprocesses the data contained in all datasets in the dataset_container
70
+ # Groups of values are averaged with respect to timecode and actual data
71
+ # Params:
72
+ # +dataset_container:: Hash that holds the datasets and further information
73
+ # +slice_size:: size of the group that averages are supposed to be calculated from
74
+ def data_preprocessing(dataset_container, slice_size)
75
+ dataset_container[:datasets].each do |dataset|
76
+ timecode = dataset.data[0]
77
+ reduced_timecode = average(timecode, slice_size)
78
+
79
+ values = dataset.data[1].map { |value| value.to_f }
80
+ reduced_values = average(values, slice_size)
81
+
82
+ dataset.data = [reduced_timecode, reduced_values]
83
+ end
84
+ end
85
+
86
+ # Create the GnuplotDataSet that is going to be printed.
87
+ # Params:
88
+ # +timecode:: Array containing the timestamps
89
+ # +values:: Array containing the actual values
90
+ # +no_plot_key:: boolean to de-/activate plotkey
91
+ # +smooth:: nil or smoothing algorithm
92
+ # +file:: file
93
+ def create_gnuplot_dataset(timecode, values, no_plot_key, smooth, file)
94
+ Gnuplot::DataSet.new([timecode, values]) do |gp_dataset|
95
+ gp_dataset.with = "lines"
96
+ if no_plot_key then
97
+ gp_dataset.notitle
98
+ else
99
+ gp_dataset.title = (File.basename file).gsub('_', '\\_')
100
+ end
101
+ gp_dataset.smooth = smooth unless smooth.nil?
102
+ end
103
+ end
104
+
105
+ def analyze_header_create_plot_title(prefix, smooth, inversion, csv_header)
106
+ plot_title = "#{prefix} over time"
107
+ if smooth then plot_title += " (smoothing: #{smooth})" end
108
+ if csv_header[2].index("Host:")
109
+ plot_title += '\n' + "(Host: #{csv_header[2][1]} User: #{csv_header[2][6]} Date: #{csv_header[3].last})"
110
+ end
111
+ if inversion then plot_title += '\n(inverted)' end
112
+ plot_title
113
+ end
114
+
115
+ def translate_to_column(category, field, csv)
116
+ category_index = csv[5].index category
117
+ if category_index.nil?
118
+ puts "'#{category}' is not a valid parameter for 'category'."
119
+ puts "Allowed categories: #{csv[5].reject{ |elem| elem == nil }.inspect}"
120
+ exit 0
121
+ end
122
+
123
+ field_index = csv[6].drop(category_index).index field
124
+ if field_index.nil?
125
+ puts "'#{field}' is not a valid parameter for 'field'."
126
+ puts "Allowed fields: #{csv[6].reject{ |elem| elem == nil }.inspect}"
127
+ exit 0
128
+ end
129
+
130
+ if $verbose then puts "'#{category}-#{field}' was translated to #{category_index + field_index}." end
131
+ column = category_index + field_index
132
+ end
133
+
134
+ # returns the values from a headerless csv file
135
+ def read_data_from_csv(files, category, field, column, no_plot_key, y_max, inversion, title, smooth)
136
+ plot_title = nil
137
+ datasets = []
138
+ autoscale = false
139
+ overall_max = y_max.nil? ? Y_DEFAULT : y_max
140
+
141
+ files.each do |file|
142
+ csv = CSV.read(file)
143
+
144
+ if column
145
+ if $verbose then puts "Reading from csv to get column #{column}." end
146
+ prefix = "dstat-column #{column}"
147
+ else
148
+ if $verbose then puts "Reading from csv to get #{category}-#{field}." end
149
+ column = translate_to_column(category, field, csv)
150
+ prefix = "#{category}-#{field}"
151
+ end
152
+
153
+ if plot_title.nil? # this only needs to be done for the first file
154
+ if title
155
+ plot_title = title
156
+ else
157
+ plot_title = analyze_header_create_plot_title(prefix, smooth, inversion != 0.0, csv[0..6])
158
+ end
159
+ end
160
+
161
+ if csv[2].index "Host:"
162
+ csv = csv.drop(7)
163
+ end
164
+
165
+ begin
166
+ csv = csv.transpose
167
+ rescue IndexError => e
168
+ puts 'ERROR: It appears that your csv file is malformed. Check for incomplete lines, empty lines etc.'
169
+ puts e.backtrace[0] + e.message
170
+ exit
171
+ end
172
+
173
+ timecode = csv[0].map { |timestamp| timestamp.to_f - csv[0].first.to_f }
174
+
175
+ values = csv[column]
176
+ if inversion != 0.0
177
+ values.map! { |value| (value.to_f - inversion).abs }
178
+ overall_max = inversion
179
+ end
180
+
181
+ if y_max.nil?
182
+ local_maximum = values.max { |a, b| a.to_f <=> b.to_f }.to_f
183
+ if local_maximum > overall_max then overall_max = local_maximum end
184
+ end
185
+
186
+ dataset = create_gnuplot_dataset(timecode, values, no_plot_key, smooth, file)
187
+ datasets.push dataset
188
+ end
189
+
190
+ if $verbose then puts "datasets: #{datasets.count} \nplot_title: #{plot_title} \ny_max: #{y_max} \nautoscale: #{autoscale}" end
191
+ dataset_container = { :datasets => datasets, :plot_title => plot_title, :y_max => overall_max, :autoscale => autoscale }
192
+ end
193
+
194
+ def read_options_and_arguments
195
+ opts = {} # Hash that holds all the options
196
+
197
+ optparse = OptionParser.new do |parser|
198
+ # banner that is displayed at the top
199
+ parser.banner = "Usage: \b
200
+ dstat_plot.rb [options] -c CATEGORY -f FIELD [directory | file1 file2 ...] or \b
201
+ dstat_plot.rb [options] -l COLUMN [directory | file1 file2 ...]\n\n"
202
+
203
+ ### options and what they do
204
+ parser.on('-v', '--verbose', 'Output more information') do
205
+ $verbose = true
206
+ end
207
+
208
+ opts[:inversion] = 0.0
209
+ parser.on('-i', '--invert [VALUE]', Float, 'Invert the graph such that inverted(x) = VALUE - f(x),', 'default is 100.') do |value|
210
+ opts[:inversion] = value.nil? ? 100.0 : value
211
+ end
212
+
213
+ opts[:no_plot_key] = false
214
+ parser.on('-n', '--no-key', 'No plot key is printed.') do
215
+ opts[:no_plot_key] = true
216
+ end
217
+
218
+ opts[:dry] = false
219
+ parser.on('-d', '--dry', 'Dry run. Plot is not saved to file but instead displayed with gnuplot.') do
220
+ opts[:dry] = true
221
+ end
222
+
223
+ opts[:output] = nil
224
+ parser.on('-o','--output FILE|DIR', 'File or Directory that plot should be saved to. ' \
225
+ 'If a directory is given', 'the filename will be generated. Default is csv file directory.') do |path|
226
+ opts[:output] = path
227
+ end
228
+
229
+ opts[:y_max]
230
+ parser.on('-y', '--y-range RANGE', Float, 'Sets the y-axis range. Default is 105. ' \
231
+ 'If a value exceeds this range,', '"autoscale" is enabled.') do |range|
232
+ opts[:y_max] = range
233
+ end
234
+
235
+ opts[:title] = nil
236
+ parser.on('-t', '--title TITLE', 'Override the default title of the plot.') do |title|
237
+ opts[:title] = title
238
+ end
239
+
240
+ opts[:smooth] = nil
241
+ parser.on('-s', '--smoothing ALGORITHM', 'Smoothes the graph using the given algorithm.') do |algorithm|
242
+ algorithms = [ 'unique', 'frequency', 'cumulative', 'cnormal', 'kdensity', 'unwrap',
243
+ 'csplines', 'acsplines', 'mcsplines', 'bezier', 'sbezier' ]
244
+ if algorithms.index(algorithm)
245
+ opts[:smooth] = algorithm
246
+ else
247
+ puts "#{algorithm} is not a valid option as an algorithm."
248
+ exit
249
+ end
250
+ end
251
+
252
+ opts[:slice_size] = nil
253
+ parser.on('-a', '--average-over SLICE_SIZE', Integer, 'Calculates the everage for slice_size large groups of values.',"\n") do |slice_size|
254
+ opts[:slice_size] = slice_size
255
+ end
256
+
257
+ opts[:category] = nil
258
+ parser.on('-c', '--category CATEGORY', 'Select the category.') do |category|
259
+ opts[:category] = category
260
+ end
261
+
262
+ opts[:field] = nil
263
+ parser.on('-f', '--field FIELD' , 'Select the field.') do |field|
264
+ opts[:field] = field
265
+ end
266
+
267
+ opts[:column] = nil
268
+ parser.on('-l', '--column COLUMN', 'Select the desired column directly.', "\n") do |column|
269
+ unless opts[:category] && opts[:field] # -c and -f override -l
270
+ opts[:column] = column.to_i
271
+ end
272
+ end
273
+
274
+ # This displays the help screen
275
+ parser.on_tail('-h', '--help', 'Display this screen.' ) do
276
+ puts parser
277
+ exit
278
+ end
279
+ end
280
+
281
+ # there are two forms of the parse method. 'parse'
282
+ # simply parses ARGV, while 'parse!' parses ARGV
283
+ # and removes all options and parameters found. What's
284
+ # left is the list of files
285
+ optparse.parse!
286
+ if $verbose then puts "opts: #{opts.inspect}" end
287
+
288
+ if opts[:category].nil? || opts[:category].nil?
289
+ if opts[:column].nil?
290
+ puts "[Error] (-c CATEGORY and -f FIELD) or (-l COLUMN) are mandatory parameters.\n\n"
291
+ puts optparse
292
+ exit
293
+ end
294
+ end
295
+
296
+ # if ARGV is empty at this point no directory or file(s) is specified
297
+ # and the current working directory is used
298
+ if ARGV.empty? then ARGV.push "." end
299
+
300
+ files = []
301
+ if File.directory?(ARGV.last) then
302
+ opts[:target_dir] = ARGV.last.chomp("/") # cuts of "/" from the end if present
303
+ files = Dir.glob "#{opts[:target_dir]}/*.csv"
304
+ files = files.sort
305
+ else
306
+ opts[:target_dir] = File.dirname ARGV.first
307
+ ARGV.each do |filename|
308
+ files.push filename
309
+ end
310
+ end
311
+ puts "Plotting data from #{files.count} file(s)."
312
+ opts[:files] = files
313
+ if $verbose then puts "files: #{files.count} #{files.inspect}" end
314
+
315
+ # opts = { :inversion, :no_plot_key, :dry, :output, :y_max, :title, :category, :field, :column, :target_dir, :files }
316
+ opts
317
+ end
318
+
319
+ class DstatPlot
320
+ def self.run
321
+ opts = read_options_and_arguments
322
+ dataset_container = read_data_from_csv(opts[:files],opts[:category], opts[:field], opts[:column],
323
+ opts[:no_plot_key], opts[:y_max], opts[:inversion], opts[:title], opts[:smooth])
324
+ data_preprocessing(dataset_container, opts[:slice_size]) unless opts[:slice_size].nil?
325
+ filename = generate_filename(opts[:output], opts[:column], opts[:category], opts[:field], opts[:target_dir])
326
+ plot(dataset_container, opts[:category], opts[:field], opts[:dry], filename)
327
+ end
328
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dstat_plot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.2
5
+ platform: ruby
6
+ authors:
7
+ - joh-mue
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gnuplot
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.6'
27
+ description: Uses gnuplot to plot csv data generated by mvneves' dstat-monitor.
28
+ email: yesiamkeen@gmail.com
29
+ executables:
30
+ - dstat-plot
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - bin/dstat-plot
35
+ - lib/dstat_plot.rb
36
+ homepage: http://github.com/citlab/dstat-tools
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 2.5.1
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Plot dstat-monitor data with gnuplot
60
+ test_files: []