data-TPCTC-Pocketdata/Rakefile

721 lines
23 KiB
Ruby

require 'rubygems'
require 'gnuplot'
$:.push("lib")
require 'json'
require 'util.rb'
require 'plot.rb'
$files = Dir["/Volumes/Storage/sqlite-logs/*.out.gz"]
task :default => :tables
plot_output :pdf
auto_open_plots
$query_actions = [
:SELECT, :DELETE, :INSERT, :UPDATE
]
class String
def escape
clone.gsub(/\"/){ |match| '\\' + match }
end
end
class IO
def lines_until
ret = []
each { |l| ret.push(l); return ret if yield(l) }
ret
end
end
def with_log_file(file)
if /\.gz$/ =~ file then
IO.popen("gunzip -c #{file}") { |f| yield f }
else
File.open(file) { |f| yield f }
end
end
def log_lines(file)
with_log_file(file) do |f|
f.map do |l|
l = l.chomp.split(/\t/)
if l.length > 9
l[8] = l[8..-1].join(/\t/)
end
l = [
nil, # User ID
[ :time, :int ], # Int timestamp
[ :order, :string ], # Ordered Timestamp
nil, # Date
[ :pid, :int ], # PID
nil, # ThreadID
nil, # ?? 'I'
nil, # Tag ID
[ :payload, :json ]
]. zip(l).
delete_if { |k,v| k == nil }.
map { |k, v|
[ k[0],
case k[1]
when :float then v.to_f
when :int then v.to_i
when :sym then v.to_sym
when :json then
begin
if v == "Unable to get app info due to security reasons"
then nil else
JSON.parse(v)
end
rescue => e
puts "Unable to parse: "+v
nil
end
else v
end
]
}.
to_h
yield(l, l[:payload])
end
end
end
task :actions do
actions =
all_log_lines do |meta, data|
[ data["Action"].to_sym, 1 ]
end.reduce { |action, cnt| cnt.sum }
puts actions.to_a.to_table(["Action", "Count"])
end
$files.each do |source|
target = source.gsub(/out.gz$/, "flat")
raise "Invalid source file: #{source}" if target == source;
file target => source do
puts "Processing #{File.basename target}"
begin
File.open(target, "w+") do |tf|
log_lines(source) do |meta, data|
if (not data.nil?) and ($query_actions.include? data["Action"].to_sym)
unless /^PRAGMA/ =~ data["Results"]
tf.puts([
meta[:pid],
meta[:time],
(/^SQLiteProgram: / =~ data["Results"]) ? 1 : 0,
data["Time"],
data["Rows returned"],
data["Results"].gsub(/^SQLiteProgram: /, "").gsub(/\n/, " ")
].join("\t"))
end
end
end
end
rescue => e
puts "Error: #{e}"
system "rm -f #{target}"
raise e
end
end
task :flatten => target
end
task :test => :compile do
# p JSON.parse(parse("SELECT GROUP_CONCAT(imagesGroupConcatSourceTable._id, ' / ') FROM R"));
p JSON.parse(parse("SELECT tree_entity._id, tree_entity.type, tree_entity.title, tree_entity.color_name, tree_entity.parent_id, tree_entity.order_in_parent, tree_entity.is_archived, (SELECT GROUP_CONCAT(imagesGroupConcatSourceTable._id, ' / ') FROM (SELECT imagesGroupConcatBlobsTable._id FROM blob AS imagesGroupConcatBlobsTable WHERE imagesGroupConcatBlobsTable.tree_entity_id = tree_entity._id AND imagesGroupConcatBlobsTable.type = 0 AND imagesGroupConcatBlobsTable.is_deleted = 0 ORDER BY imagesGroupConcatBlobsTable.time_created DESC LIMIT 6) imagesGroupConcatSourceTable), (SELECT COUNT(*) FROM blob AS blobCountTable WHERE tree_entity._id = blobCountTable.tree_entity_id AND blobCountTable.is_deleted = 0 AND blobCountTable.type = 0), (SELECT COUNT(*) FROM blob AS blobCountTable WHERE tree_entity._id = blobCountTable.tree_entity_id AND blobCountTable.is_deleted = 0 AND blobCountTable.type = 1), (SELECT GROUP_CONCAT(CASE WHEN groupConcatSourceTable.is_checked IS NULL THEN '0' ELSE REPLACE(REPLACE(groupConcatSourceTable.is_checked, ':', '::'), '/', '//') END || ' : ' || CASE WHEN groupConcatSourceTable.text IS NULL THEN '' ELSE REPLACE(REPLACE(groupConcatSourceTable.text, ':', '::'), '/', '//') END, ' / ') FROM (SELECT grpConcatListItemTable.is_checked, grpConcatListItemTable.text FROM list_item AS grpConcatListItemTable WHERE grpConcatListItemTable.list_parent_id = tree_entity._id AND grpConcatListItemTable.is_deleted = 0 ORDER BY grpConcatListItemTable.order_in_parent DESC, grpConcatListItemTable.time_last_updated DESC LIMIT 5) groupConcatSourceTable), reminder.reminder_type, reminder.reminder_state, reminder.julian_day, reminder.time_of_day, reminder.time_period, reminder.location_type, reminder.location_name, reminder.longitude, reminder.latitude, reminder.radius, reminder.location_address, reminder.location_reference, tree_entity.account_id, alert.state, tree_entity.version FROM tree_entity LEFT OUTER JOIN reminder ON tree_entity._id = reminder.tree_entity_id AND reminder.is_deleted = 0 LEFT OUTER JOIN alert ON alert.reminder_id = reminder._id WHERE tree_entity.is_deleted = 0 AND tree_entity._id IN null GROUP BY tree_entity._id"))
end
file $parser_jar => Dir["src/**/*.java"] { system("ant jar") or raise "Build Failed"; }
task :compile => $parser_jar
$stats = Hash.new { |h,k| h[k] = Hash.new }
$stat_classes = ["SELECT", "INSERT", "UPSERT", "UPDATE", "DELETE"]
task :loadStats do
File.open("graphs/stats") do |f|
segment = nil
field_name = nil;
field_value = nil;
attr_name = nil;
attr_value = nil;
f.each do |l|
case l.chomp
when /----- ([a-zA-Z]+) Stats -----/ then segment = $1
when / ([^:]+): +([0-9.]+)/ then
field_name, field_value = $1, $2.to_f
$stats[segment][field_name] = field_value
when / ([^:]+): *$/ then
field_name, field_value = $1, Hash.new
$stats[segment][field_name] = field_value
when / +\| *([0-9]+) +-> *([^ ]+)/
attr_name, attr_value = $1.to_i, $2.to_f
field_value[attr_name] = attr_value
when / +\| *<([0-9]+), *([0-9]+)> +-> *([^ ]+)/
attr_name1, attr_name2, attr_value = $1.to_i, $2.to_i, $3.to_f
field_value[attr_name1] = Hash.new unless field_value.has_key? attr_name1;
field_value[attr_name1][attr_name2] = attr_value
when / +\| *(.+) +-> *([^ ]+)/
attr_name, attr_value = $1, $2.to_f
field_value[attr_name] = attr_value
when / +\| *class ([^ ]+) +-> *([^ ]+)/
attr_name, attr_value = $1, $2.to_f
field_value[attr_name] = attr_value
when / +>(.*)/ then
note = $1
field_value[:notes] = Array.new unless field_value.has_key? :notes
field_value[:notes].push note
else raise "Huh? : #{l.chomp}"
end
end
end
end
["SELECT", "DELETE", "UPDATE"].each do |segment|
plot "graphs/#{segment.downcase}_breakdown_by_width" => :loadStats do |graph|
pretty_plot(graph, logy: true, fontsize: 13, border: [:all], bordercolor: 'rgb "#000000"')
graph.yrange '[0.1:100000000]'
graph.xlabel 'Number of Tables Accessed'
graph.ylabel "Number of #{segment} Queries"
counts, qcounts =
$stats[segment]["Query Counts by Number of Tables Referenced"].to_a.
sort { |a,b| a[0] <=> b[0] }.
unzip
draw_bar_plot(graph,
data: qcounts,
labels: counts,
box_style: lambda { |i| "boxes fill solid lc rgb \"#A00000\""},
bar_width: 15
)
end
task :graphs => "graphs/#{segment.downcase}_breakdown_by_width"
end
plot "graphs/update_target_cols" => :loadStats do |graph|
pretty_plot(graph, logy: true, sizex: 8, fontsize: 13, border: [:all], bordercolor: 'rgb "#000000"', gridcolor: 'rgb "#000000"')
graph.xlabel 'Number of Columns Updated'
graph.ylabel 'Number of UPDATE Statements'
graph.format 'y "%1.1f'
counts, qcounts =
$stats["UPDATE"]["Update Target Columns"].to_a.
sort { |a,b| a[0] <=> b[0] }.
unzip
draw_bar_plot(graph, data: qcounts, labels: counts, box_style: lambda { |i| "boxes fill solid lc rgb \"#A00000\""})
end
task :graphs => "graphs/update_target_cols"
plot "graphs/spj_breakdown_by_width" => :loadStats do |graph|
pretty_plot(graph, :logy => true)
graph.yrange '[1:100000000]'
graph.xlabel 'Number of Tables Accessed'
graph.ylabel 'Number of Select/Project/Join Queries'
counts, qcounts =
$stats["SELECT"]["SPJ Query Counts by Join Width"].to_a.
sort { |a,b| a[0] <=> b[0] }.
unzip
draw_bar_plot(graph, data: qcounts, labels: counts, box_style: lambda { |i| "boxes fill solid lc rgb \"#A00000\""})
end
task :graphs => "graphs/spj_breakdown_by_width"
["SELECT", "UPDATE"].each do |segment|
plot "graphs/#{segment.downcase}_breakdown_by_nesting" => :loadStats do |graph|
pretty_plot(graph, logy: true, fontsize: 13, border: [:all], bordercolor: 'rgb "#000000"')
graph.yrange '[1:100000000]'
graph.xlabel 'Maximum Nesting Depth'
graph.ylabel "Number of #{segment} Queries"
counts, qcounts =
$stats[segment]["Query Counts by Query Nesting Depth"].to_a.
sort { |a,b| a[0] <=> b[0] }.
unzip
draw_bar_plot(graph,
data: qcounts,
labels: counts,
box_style: lambda { |i| "boxes fill solid lc rgb \"#A00000\""},
bar_width: 15
)
end
task :graphs => "graphs/#{segment.downcase}_breakdown_by_nesting"
end
line_plot "graphs/select_count_cdf_by_app" => :loadStats do |graph|
pretty_plot(graph, logx: true, fontsize: 12, border: [:all], bordercolor: 'rgb "#000000"')
graph.key 'off'
graph.grid 'front'
graph.unset 'grid'
graph.format 'x "10^%T"'
graph.format 'y "%1.1f'
# puts "#{$stats["SELECT"]["Select queries by app"]["???"].to_i} queries (#{($stats["SELECT"]["Select queries by app"]["???"].to_f / $stats["SELECT"]["Total Queries"].to_f * 100).round(2)}%) can not be associated with an app"
graph.ylabel "CDF"
graph.xlabel "Number of SELECT Queries Issued"
{
:data =>
$stats["SELECT"]["Query Counts by App"].
to_a.
delete_if { |name,cnt| name == "???" }.
sort { |a,b| a[1] <=> b[1] }.
map { |name, cnt| cnt.to_i }.
cdf,
:with => [ "lines lc 'blue' lw 1.5" ]
}
end
task :graphs => "graphs/select_count_cdf_by_app"
line_plot "graphs/select_percent_simple_cdf_by_app" => :loadStats do |graph|
pretty_plot(graph, logy: false, fontsize: 12, border: [:all], bordercolor: 'rgb "#000000"')
graph.key 'off'
graph.grid 'front'
graph.unset 'grid'
graph.format 'x "%g%%"'
graph.format 'y "%1.1f'
graph.ylabel "CDF"
graph.xlabel "% of SELECT Queries That Are Key-Value Queries"
simple = $stats["SELECT"]["Simple Select queries by app"]
all = $stats["SELECT"]["Query Counts by App"]
trivial_apps = all.keys.clone.delete_if { |k| all[k].to_i != simple[k].to_i }
puts "#{trivial_apps.size} out of #{all.size} apps (#{(trivial_apps.size.to_f / all.size.to_f * 100).round(2)}%) do not pose complex queries"
puts trivial_apps.sort.join(", ")
{
:data =>
all.to_a.
delete_if { |name,cnt| name == "???" }.
map { |name, cnt| (simple[name].to_f / cnt.to_f)*100 }.
sort.
cdf,
:with => [ "lines lc 'blue' lw 1.5" ]
}
end
task :graphs => "graphs/select_percent_simple_cdf_by_app"
line_plot "graphs/data_mod_ops_cdf_by_app" => :loadStats do |graph|
pretty_plot(graph, logx: true, fontsize: 12, border: [:all], bordercolor: 'rgb "#000000"')
graph.key 'off'
graph.grid 'front'
graph.unset 'grid'
graph.format 'x "10^%T"'
graph.format 'y "%1.1f'
graph.ylabel "CDF"
graph.xlabel "Number of Data Manipulation Statements"
# graph.yrange "[0.9:1]"
all = $stats["Global"]["Query Counts by App"]
select = $stats["SELECT"]["Query Counts by App"]
{
:data =>
all.keys.clone.
map { |k| all[k].to_i - select.fetch(k, 0).to_i unless k == "???" }.
compact.
sort.
cdf,
:with => [ "lines lc 'blue' lw 1.5" ]
}
end
task :graphs => "graphs/data_mod_ops_cdf_by_app"
line_plot "graphs/read_write_ratio_cdf_by_app" => :loadStats do |graph|
pretty_plot(graph, logy: false, fontsize: 12, border: [:all], bordercolor: 'rgb "#000000"')
graph.key 'off'
graph.grid 'front'
graph.unset 'grid'
graph.format 'x "%g%%"'
graph.format 'y "%1.1f'
graph.ylabel "CDF"
graph.xlabel "Read/Write Ratio (100% = All Reads)"
all = $stats["Global"]["Query Counts by App"]
select = $stats["SELECT"]["Query Counts by App"]
ratios =
all.keys.clone.
map { |k| [k, (select.fetch(k, 0).to_f / all[k].to_f) * 100] unless k == "???" }.
compact.
sort { |a,b| a[1] <=> b[1] }
{
:data => ratios.map { |k,ratio| ratio }.cdf,
:with => [ "lines lc 'blue' lw 1.5" ]
}
end
task :rw_stats => :loadStats do
all = $stats["Global"]["Query Counts by App"]
select = $stats["SELECT"]["Query Counts by App"]
ratios =
all.keys.clone.
map { |k| [k, (select.fetch(k, 0).to_f / all[k].to_f) * 100] unless k == "???" }.
compact.
sort { |a,b| a[1] <=> b[1] }
read_only_apps = ratios.map { |k, ratio| k if ratio >= 100.0 }.compact
puts "#{read_only_apps.length} read only apps (#{(read_only_apps.length.to_f / all.keys.length.to_f * 100).round(2)}%), performing #{read_only_apps.map { |k| all[k] }.sum.to_i} ops"
puts read_only_apps.sort.join(", ")
write_only_apps = ratios.map { |k, ratio| k if ratio <= 0.0 }.compact
puts "#{write_only_apps.length} write only apps (#{(write_only_apps.length.to_f / all.keys.length.to_f * 100).round(2)}%)"
puts write_only_apps.join(", ")
end
["Global", "SELECT"].each do |segment|
task "#{segment.downcase}_top" => :loadStats do
all = $stats[segment]["Query Counts by App"].to_a.clone.delete_if { |name, cnt| name == "???" }.sort { |a, b| b[1] <=> a[1] }
puts "---- TOP 10 ----"
puts all[0..10].map { |name,cnt| "#{name} (#{cnt.to_i})" }.join("\n")
puts "---- BOTTOM 10 ----"
puts all[-10..-1].map { |name,cnt| "#{name} (#{cnt.to_i})" }.join("\n")
end
task "#{segment.downcase}_top_all" => :loadStats do
all = $stats[segment]["Query Counts by App"].to_a.clone.delete_if { |name, cnt| name == "???" }.sort { |a, b| b[1] <=> a[1] }
all.each.with_index { |x, i| puts "#{i}: #{x[0]}"}
end
end
class Numeric
def to_delim_s(delim = ",")
to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1'+delim).reverse
end
end
def format_int(x)
x = 0 if x.nil?
if x == 0 then "" else "\\ \\ #{x.to_i.to_delim_s}\\ \\ " end
end
def format_float(x, rnd = 2)
if x.nil? then "" else
"\\ \\ #{x.to_f.round(rnd)}\\ \\ "
end
end
class LatexTableWriter
def initialize(schema, args = {})
@data = ""
@box = args.fetch(:box, true)
line = args.fetch(:overline, @box)
@row_lines = args.fetch(:row_lines, true)
put("\\begin{tabular}{#{schema}}#{if line then "\\hline" else "" end}\n")
@firstcell = true;
end
def put(d)
@data += d.to_s
end
def cell(d)
put " & " unless @firstcell
put d
@firstcell = false;
end
def col_span(cols, schema, data)
cell "\\multicolumn{#{cols}}{#{schema}}{#{data}}"
end
def endrow(line = @row_lines)
put "\\\\#{if line then "\\hline" else "" end}\n"
@firstcell = true;
end
def partial_row(row)
row.each { |d| cell(d) }
end
def row(row, line = @row_lines)
partial_row(row)
endrow(line)
end
def to_s
@data+"\\end{tabular}"
end
def write(io)
case io
when IO then io.puts to_s
when String then File.open(io, "w+") { |f| f.puts to_s }
else raise "Invalid write target: #{io}"
end
end
end
def latex_bold(b)
"\\textbf{#{b}}"
end
file "graphs/spjsort_by_width_and_where.tex" => :loadStats do
width_and_where =
$stats["SELECT"]["SP[+Sort] Query Counts by Join Width and Number of Conjunctive Where Clauses"]
where_keys = width_and_where.map { |width, wheres| wheres.keys }.flatten.uniq.sort
width_keys = width_and_where.keys.sort;
width_totals = width_keys.map { |k| [k, 0] }.to_h
table = LatexTableWriter.new("|c#{"|c"*width_keys.length}|c|");
table.cell("")
table.col_span(width_keys.length, "|c|", latex_bold("Join Width"))
table.cell("")
table.endrow
table.cell(latex_bold("Where Clauses"))
table.partial_row(width_keys)
table.cell(latex_bold("Total"));
table.endrow
where_keys.each do |wh_k|
row = width_keys.map { |wi_k| "#{format_int(width_and_where[wi_k][wh_k])}" }
row_total = width_keys.map { |wi_k| width_and_where[wi_k][wh_k] or 0 }.sum
width_keys.each { |wi_k| width_totals[wi_k] += width_and_where[wi_k][wh_k] unless width_and_where[wi_k][wh_k].nil? }
table.cell(wh_k)
table.partial_row(row)
table.cell(latex_bold(format_int(row_total)))
table.endrow
end
row = width_keys.map { |k| width_totals[k] }
table.cell(latex_bold("Total"))
table.partial_row(row.map { |i| latex_bold(format_int(i)) })
table.cell(latex_bold(format_int(row.sum)))
table.endrow
table.write("graphs/spjsort_by_width_and_where.tex")
end
task :tables => "graphs/spjsort_by_width_and_where.tex"
$condition_types =
[
[["CONST_CMP_EQUALITY"],
"Const $=$ Expr", "Exact Lookups"],
[["net.sf.jsqlparser.schema.Column", "NOT-net.sf.jsqlparser.schema.Column"],
"[NOT] Column", "Boolean Column Cast"],
[["net.sf.jsqlparser.expression.operators.relational.LikeExpression",
"NOT-net.sf.jsqlparser.expression.operators.relational.LikeExpression"],
"Expr [NOT] LIKE Pattern", "Patterned String Lookup"],
[["CONST_EQUALITY","CONST_INEQUALITY","net.sf.jsqlparser.expression.LongValue"],
"Const or (Const = Const)", "No-op Clause"],
[["CMP_EQUALITY"], "Expr $=$ Expr", "Other Equality"],
[["CMP_INEQUALITY"], "Expr $\\theta$ Expr", "Other Inequality"],
[["CONST_CMP_INEQUALITY", "NOT-CONST_CMP_INEQUALITY"], "Const $\\theta$ Expr", "Inequality on 1 constant"],
[["DISJUNCTION", "NOT-DISJUNCTION", "NOT-net.sf.jsqlparser.expression.operators.conditional.AndExpression"], "[NOT] Expr $\\vee$ Expr", "Disjunction"],
[["INEXPRESSION","NOT-INEXPRESSION"], "Expr [NOT] IN (List or Query)", "Membership Test"],
[["NOT-net.sf.jsqlparser.expression.Function", "net.sf.jsqlparser.expression.Function"],
"Function(Expr)", "Function Call"],
[["net.sf.jsqlparser.expression.operators.relational.IsNullExpression"],
"Expr IS [NOT] NULL", "Validity Test"],
[["net.sf.jsqlparser.expression.InverseExpression"],
"NOT Expr", "Negation"],
[["net.sf.jsqlparser.expression.operators.relational.Between"],
"Expr BETWEEN Const AND Const", "Range Test"],
[["net.sf.jsqlparser.expression.operators.relational.ExistsExpression",
"NOT-net.sf.jsqlparser.expression.operators.relational.ExistsExpression"],
"[NOT] EXISTS (Query)", "Subquery Membership"],
[["net.sf.jsqlparser.expression.CaseExpression"],
"CASE WHEN \\ldots", "Functional If-Then-Else"],
[["net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd",
"NOT-net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd"
],
"Expr \\& Expr", "Bitwise AND"],
].map { |label, example, descriptor| [label, "\\texttt{#{example}}", descriptor]}
file "graphs/sp_trivial_condition_breakdown.tex" => :loadStats do
breakdown =
$stats["SELECT"]["SP[+Sort] With Lone Where Clause by Clause Type"]
table = LatexTableWriter.new("c|c|c", box: false, row_lines: false);
table.row [
latex_bold("Expression Type"),
latex_bold("Expression Form"),
latex_bold("Count")
], true
known_cols = $condition_types.map { |ks, e, d| ks }.flatten;
breakdown.keys.each { |k| raise "Unknown category: '#{k}'" unless known_cols.include? k }
$condition_types.map do |ks, example, descriptive|
counts = ks.map { |k| if breakdown.has_key? k then breakdown[k].to_i else nil end }.compact;
unless counts.empty?
[ descriptive, example.gsub(/ or Query/, ""), counts.sum ]
end
end.compact.
sort { |a,b| b[2] <=> a[2] }.
map { |descriptive,example,count| [descriptive,example,format_int(count)] }.
each { |row| table.row row }
table.write("graphs/sp_trivial_condition_breakdown.tex")
end
task :tables => "graphs/sp_trivial_condition_breakdown.tex"
["SELECT", "DELETE", "UPDATE"].each do |segment|
file "graphs/#{segment.downcase}_condition_breakdown.tex" => :loadStats do
breakdown =
$stats[segment]["Query Counts by Use of Each Where Clause Type"]
table = LatexTableWriter.new("c|c|c", box: false, row_lines: false);
table.row [
latex_bold("Expression Type"),
latex_bold("Expression Form"),
latex_bold("Count")
], true
known_cols = $condition_types.map { |ks, e, d| ks }.flatten;
breakdown.keys.each { |k| raise "Unknown category: '#{k}'" unless known_cols.include? k }
$condition_types.map do |ks, example, descriptive|
counts = ks.map { |k| if breakdown.has_key? k then breakdown[k].to_i else nil end }.compact;
unless counts.empty?
[ descriptive, example, counts.sum ]
end
end.compact.
sort { |a,b| b[2] <=> a[2] }.
map { |descriptive,example,count| [descriptive,example,format_int(count)] }.
each { |row| table.row row }
table.write("graphs/#{segment.downcase}_condition_breakdown.tex")
end
task :tables => "graphs/#{segment.downcase}_condition_breakdown.tex"
end
["SELECT"].each do |segment|
file "graphs/#{segment.downcase}_functions.tex" => :loadStats do
breakdown =
$stats[segment]["Call Sites per Function"]
table = LatexTableWriter.new("|c|c|");
table.row [
latex_bold("Function"),
latex_bold("Call Sites")
]
breakdown.to_a.
sort { |a,b| b[1] <=> a[1] }.
map { |fname,count| [fname.gsub(/_/,"\\_"), format_int(count.to_i) ] }.
each { |row| table.row row }
table.write("graphs/#{segment.downcase}_functions.tex")
end
task :tables => "graphs/#{segment.downcase}_functions.tex"
end
file "graphs/query\_breakdown.tex" => :loadStats do
keys = ["Total"] + $stats["Global"].keys.map do |k|
case k
when /Total Queries/ then nil
when /WITH Queries/ then nil
when /(.*) Queries$/ then $1
end
end.compact
breakdown =
($stat_classes + ["Global"]).
map { |segment| keys.map { |k| $stats[segment]["#{k} Queries"].to_i } }.
unzip
table = LatexTableWriter.new("|r|#{"c|"*$stat_classes.length}c|");
table.row(["\\ \\ \\textbf{Operation}\\ \\ "] + $stat_classes.map {|x| "\\texttt{#{x}}" } + ["\\textbf{Total}"])
table.cell latex_bold("\\ \\ Count ")
table.row(breakdown.shift.map { |x| format_int x })
table.row([latex_bold("\\ \\ Runtime (ms) ")] +
$stat_classes.map { |segment| format_float($stats[segment]["Average Runtime"]) } +
[""]
)
table.col_span($stat_classes.length+2, "|c|", latex_bold("Features Used"))
table.endrow
breakdown.zip(keys[1..-1]).each do |row, key|
table.cell "\\texttt{#{key.gsub(/GROUP BY Aggregate/, "GROUP BY")}}"
table.row(row.map { |x| format_int(x) })
end
table.write("graphs/query\_breakdown.tex")
end
task :tables => "graphs/query\_breakdown.tex"
# def plot(type, args)
# fname =
# case args
# when Hash then args.keys[0]
# when String solid
# else raise "Invalid plot arguments: #{args}"
# end
# file(args) do
# g = type.new
# g.theme_greyscale
# yield g
# g.write(fname)
# system("open #{fname}")
# end
# task :graphs => fname
# end
# plot Gruff::Pie, "graphs/breakdown_by_category.pdf" => :loadStats do |g|
# g.title = "Breakdown by SQL/SDL Operation"
# $stat_classes.each do |qtype|
# g.data qtype, $stats[qtype]["Total Queries"]
# end
# end
# ["SELECT"].each do |segment|
# plot Gruff::Bar, "graphs/#{segment.downcase}_breakdown_by_width.pdf" => :loadStats do |g|
# g.title = "Join Width in #{segment} Queries"
# counts, qcounts =
# $stats[segment]["Query Counts by Number of Tables Referenced"].to_a.
# sort { |a,b| a[0] <=> b[0] }.
# unzip
# g.minimum_value = 0;
# g.maximum_value = Math.log10(qcounts.max).ceil
# # counts.shift
# g.labels = counts.map.with_index { |k,v| [v, k] }.to_h
# g.data(:SELECT, qcounts.map {|i| Math.log10(i) })
# end
# end