721 lines
23 KiB
Ruby
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
|