Skip to content

Commit

Permalink
Migrate serialization format to JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
ararslan committed Oct 20, 2017
1 parent 1238af0 commit 66ef083
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 45 deletions.
2 changes: 1 addition & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
julia 0.6
Compat 0.26
JLD 0.6.6
JSON
12 changes: 2 additions & 10 deletions src/BenchmarkTools.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
module BenchmarkTools

using Compat
import JLD

# `show` compatibility for pre-JuliaLang/julia#16354 builds
if VERSION < v"0.5.0-dev+4305"
Base.get(io::IO, setting::Symbol, default::Bool) = default
end

if VERSION >= v"0.6.0-dev.1015"
using Base.Iterators
end
using JSON
using Base.Iterators

const BENCHMARKTOOLS_VERSION = v"0.0.6"

Expand Down
143 changes: 115 additions & 28 deletions src/serialization.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
const VERSION_KEY = "__versions__"
const VERSIONS = Dict("Julia" => string(VERSION),
"BenchmarkTools" => string(BENCHMARKTOOLS_VERSION))

const VERSIONS = Dict("Julia" => string(VERSION), "BenchmarkTools" => string(BENCHMARKTOOLS_VERSION))
# TODO: Add any new types as they're added
const supported_types = [Benchmark, BenchmarkGroup, Parameters, TagFilter, Trial,
TrialEstimate, TrialJudgement, TrialRatio]

for T in supported_types
@eval function JSON.lower(x::$T)
S = typeof(x) # T could be a UnionAll
[string(S), Dict(fieldname($T, i) => getfield(x, i) for i = 1:fieldcount($T))]
end
end

mutable struct ParametersPreV006
seconds::Float64
Expand All @@ -21,36 +31,113 @@ mutable struct TrialPreV006
allocs::Int
end

function JLD.readas(p::ParametersPreV006)
return Parameters(p.seconds, p.samples, p.evals, Float64(p.overhead), p.gctrial,
p.gcsample, p.time_tolerance, p.memory_tolerance)
function recover(x::Vector)
length(x) == 2 || throw(ArgumentError("Expecting a vector of length 2"))
typename = x[1]::String
fields = x[2]::Dict
T = eval(parse(typename))::Type
fc = fieldcount(T)
xs = Vector{Any}(fc)
for i = 1:fc
ft = fieldtype(T, i)
fn = String(fieldname(T, i))
xs[i] = if ft in supported_types
recover(fields[fn])
else
convert(ft, fields[fn])
end
end
T(xs...)
end

function JLD.readas(t::TrialPreV006)
new_times = convert(Vector{Float64}, t.times)
new_gctimes = convert(Vector{Float64}, t.gctimes)
return Trial(t.params, new_times, new_gctimes, t.memory, t.allocs)
function save(filename::AbstractString, args...)
isempty(args) && throw(ArgumentError("Nothing to save"))
if !endswith(filename, ".json")
noext, ext = splitext(filename)
msg = if ext == ".jld"
"JLD serialization is no longer supported. Benchmarks should now be saved\n" *
"in JSON format using `save(\"$noext\".json, args...)`. You will need to\n" *
"convert existing saved benchmarks to JSON in order to deserialize them with\n" *
"this version of BenchmarkTools."
else
"Only JSON serialization is supported. Use `save(\"$noext.json\", args...)`."
end
throw(ArgumentError(msg))
end
goodargs = Any[]
for arg in args
if arg isa String
warn("Naming variables in serialization is no longer supported.\nThe name " *
"will be ignored and the object will be serialized in the order it appears " *
"in the input.")
continue
elseif !any(T->arg isa T, supported_types)
throw(ArgumentError("Only BenchmarkTools types can be serialized."))
end
push!(goodargs, arg)
end
isempty(goodargs) && error("Nothing to save")
open(filename, "w") do io
JSON.print(io, [VERSIONS, goodargs])
end
end

function save(filename, args...)
JLD.save(filename, VERSION_KEY, VERSIONS, args...)
JLD.jldopen(filename, "r+") do io
JLD.addrequire(io, BenchmarkTools)
function load(filename::AbstractString, args...)
if !endswith(filename, ".json")
noext, ext = splitext(filename)
msg = if ext == ".jld"
"JLD deserialization is no longer supported. Benchmarks should now be saved\n" *
"in JSON format using `save(\"$noext\".json, args...)`. You will need to\n" *
"convert existing saved benchmarks to JSON in order to deserialize them with\n" *
"this version of BenchmarkTools."
else
"Only JSON deserialization is supported. Use `load(\"$noext.json\", args...)`."
end
throw(ArgumentError(msg))
end
if !isempty(args)
throw(ArgumentError("Looking up deserialized values by name is no longer supported, " *
"as names are no longer saved."))
end
return nothing
parsed = open(JSON.parse, filename, "r")
if !isa(parsed, Vector) || length(parsed) != 2 || !isa(parsed[1], Dict) || !isa(parsed[2], Vector)
error("Unexpected JSON format. Was this file produced by BenchmarkTools?")
end
versions = parsed[1]::Dict
values = parsed[2]::Vector
map!(recover, values, values)
end

@inline function load(filename, args...)
# no version-based rules are needed for now, we just need
# to check that version information exists in the file.
if JLD.jldopen(file -> JLD.exists(file, VERSION_KEY), filename, "r")
result = JLD.load(filename, args...)
else
JLD.translate("BenchmarkTools.Parameters", "BenchmarkTools.ParametersPreV006")
JLD.translate("BenchmarkTools.Trial", "BenchmarkTools.TrialPreV006")
result = JLD.load(filename, args...)
JLD.translate("BenchmarkTools.Parameters", "BenchmarkTools.Parameters")
JLD.translate("BenchmarkTools.Trial", "BenchmarkTools.Trial")
end
return result
end
#function JLD.readas(p::ParametersPreV006)
# return Parameters(p.seconds, p.samples, p.evals, Float64(p.overhead), p.gctrial,
# p.gcsample, p.time_tolerance, p.memory_tolerance)
#end
#
#function JLD.readas(t::TrialPreV006)
# new_times = convert(Vector{Float64}, t.times)
# new_gctimes = convert(Vector{Float64}, t.gctimes)
# return Trial(t.params, new_times, new_gctimes, t.memory, t.allocs)
#end
#
#function save(filename, args...)
# JLD.save(filename, VERSION_KEY, VERSIONS, args...)
# JLD.jldopen(filename, "r+") do io
# JLD.addrequire(io, BenchmarkTools)
# end
# return nothing
#end
#
#@inline function load(filename, args...)
# # no version-based rules are needed for now, we just need
# # to check that version information exists in the file.
# if JLD.jldopen(file -> JLD.exists(file, VERSION_KEY), filename, "r")
# result = JLD.load(filename, args...)
# else
# JLD.translate("BenchmarkTools.Parameters", "BenchmarkTools.ParametersPreV006")
# JLD.translate("BenchmarkTools.Trial", "BenchmarkTools.TrialPreV006")
# result = JLD.load(filename, args...)
# JLD.translate("BenchmarkTools.Parameters", "BenchmarkTools.Parameters")
# JLD.translate("BenchmarkTools.Trial", "BenchmarkTools.Trial")
# end
# return result
#end
66 changes: 60 additions & 6 deletions test/SerializationTests.jl
Original file line number Diff line number Diff line change
@@ -1,14 +1,68 @@
module SerializationTests

using Base.Test
using Compat
using Compat.Test
using BenchmarkTools

old_data = BenchmarkTools.load(joinpath(dirname(@__FILE__), "data_pre_v006.jld"), "results")
BenchmarkTools.save(joinpath(dirname(@__FILE__), "tmp.jld"), "results", old_data)
new_data = BenchmarkTools.load(joinpath(dirname(@__FILE__), "tmp.jld"), "results")
eq(x::T, y::T) where {T<:Union{BenchmarkTools.supported_types...}} =
all(i->eq(getfield(x, i), getfield(y, i)), 1:fieldcount(T))
eq(x::T, y::T) where {T} = isapprox(x, y)

@test old_data == new_data
tmp = joinpath(@__DIR__, "tmp.json")
isfile(tmp) && rm(tmp)

rm(joinpath(dirname(@__FILE__), "tmp.jld"))
@testset "Successful (de)serialization" begin
b = @benchmarkable sin(1)
tune!(b)
bb = run(b)

BenchmarkTools.save(tmp, b, bb)
@test isfile(tmp)
rm(tmp)

results = BenchmarkTools.load(tmp)
@test results isa Vector{Any}
@test length(results) == 2
@test eq(results[1], b)
@test eq(results[2], bb)
end

@testset "Deprecated behaviors" begin
@test_throws ArgumentError BenchmarkTools.save("x.jld", b)
@test_throws ArgumentError BenchmarkTools.save("x.txt", b)
@test_throws ArgumentError BenchmarkTools.save("x.json")
@test_throws ArgumentError BenchmarkTools.save("x.json", 1)

@test_warn "Naming variables" BenchmarkTools.save(tmp, "b", b)
@test isfile(tmp)
results = BenchmarkTools.load(tmp)
@test length(results) == 1
@test eq(results[1], b)
rm(tmp)

@test_throws ArgumentError BenchmarkTools.load("x.jld")
@test_throws ArgumentError BenchmarkTools.load("x.txt")
@test_throws ArgumentError BenchmarkTools.load("x.json", "b")
end

@testset "Error checking" begin
isfile(tmp) && rm(tmp)
open(tmp, "w") do f
print(f, """
{"never":1,[{"gonna":2,"give":3,"you":4,"up":5}]}
""")
end
try
BenchmarkTools.load(tmp)
error("madness")
catch err
# This function thows a bunch of ArgumentErrors, so test for this specifically
@test err isa ArgumentError
@test contains(err.msg, "Unexpected JSON format")
end
rm(tmp)

@test_throws ArgumentError BenchmarkTools.recover([1])
end

end # module
Binary file removed test/data_pre_v006.jld
Binary file not shown.

0 comments on commit 66ef083

Please sign in to comment.