Skip to content

Commit

Permalink
Initial working commit
Browse files Browse the repository at this point in the history
  • Loading branch information
timholy committed Apr 11, 2016
1 parent dc185f6 commit dbb621b
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 3 deletions.
1 change: 1 addition & 0 deletions REQUIRE
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
julia 0.4
Compat
147 changes: 146 additions & 1 deletion src/DebuggingUtilities.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,150 @@
__precompile__()

module DebuggingUtilities

# package code goes here
using Compat

export @showln, test_showline, time_showline

"""
DebuggingUtilities contains a few tools that may help debug julia code. The
exported tools are:
- `@showln`: a macro for displaying variables and corresponding function, file, and line number information
- `test_showline`: a function that displays progress as it executes a file
- `time_showline`: a function that displays execution time for each expression in a file
"""
DebuggingUtilities

## @showln

btvalid(lkup) = !isempty(lkup)
btfrom_c(lkup) = lkup[length(lkup)-1]

if VERSION < v"0.5.0-dev"
function print_btinfo(io, lkup)
funcname, file, line = lkup
print(io, "in ", funcname, " at ", file, ", line ", line)
end
else
function print_btinfo(io, lkup)
funcname = lkup[1]
print(io, "in ", funcname)
for i = 2:2:length(lkup)-3
print(io, " at ", lkup[i], ":", lkup[i+1])
end
end
end

function show_backtrace1(io, bt)
for t in bt
lkup = ccall(:jl_lookup_code_address, Any, (Ptr{Void}, Int32), t, 0)
if btvalid(lkup) && !btfrom_c(lkup)
funcname = lkup[1]
if funcname != :backtrace
print_btinfo(io, lkup)
break
end
end
end
end

const showlnio = Ref{IO}(STDOUT)

"""
`@showln x` prints "x = val", where `val` is the value of `x`, along
with information about the function, file, and line number at which
this statement was executed. For example:
function foo()
x = 5
@showln x
x = 7
@showln x
nothing
end
might produce output like
x = 5
(in foo at ./error.jl:26 at /tmp/showln_test.jl:52)
x = 7
(in foo at ./error.jl:26 at /tmp/showln_test.jl:54)
This macro causes a backtrace to be taken, and looking up the
corresponding code information is relatively expensive, so using
`@showln` can have a substantial performance cost.
The indentation of the line is proportional to the length of the
backtrace, and consequently is an indication of recursion depth.
Line numbers are not typically correct on julia-0.4.
"""
macro showln(exs...)
blk = Expr(:block)
for ex in exs
push!(blk.args, :(println(showlnio[], " "^indent, sprint(Base.show_unquoted,$(Expr(:quote, ex)),indent)*" = ", repr(begin value=$(esc(ex)) end))))
end
if !isempty(exs); push!(blk.args, :value); end
quote
local bt = backtrace()
local indent = length(bt) # to mark recursion
$blk
print(showlnio[], " "^indent*"(")
show_backtrace1(showlnio[], bt)
println(showlnio[], ")")
end
end

"""
`test_showline(filename)` is equivalent to `include(filename)`, except
that it also displays the expression and file-offset (in characters) for
each expression it executes. This can be useful for debugging errors,
especially those that cause a segfault.
"""
function test_showline(filename)
str = @compat readstring(filename)
eval(Main, parse("using Base.Test"))
idx = 1
while idx < length(str)
ex, idx = parse(str, idx)
try
println(idx, ": ", ex)
end
eval(Main,ex)
end
println("done")
end

"""
`time_showline(filename)` is equivalent to `include(filename)`, except
that it also analyzes the time expended on each expression within the
file. Once finished, it displays the file-offset (in characters),
elapsed time, and expression in order of increasing duration. This
can help you identify bottlenecks in execution.
This is less useful now that julia has package precompilation, but can
still be handy on occasion.
"""
function time_showline(filename)
str = @compat readstring(filename)
idx = 1
exprs = Array(Any, 0)
t = Array(Float64, 0)
pos = Array(Int, 0)
while idx < length(str)
ex, idx = parse(str, idx)
push!(exprs, ex)
push!(t, (tic(); eval(Main,ex); toq()))
push!(pos, idx)
end
perm = sortperm(t)
for p in perm
print(pos[p], " ", t[p], ": ")
str = string(exprs[p])
println(split(str, '\n')[1])
end
println("done")
end

end # module
2 changes: 2 additions & 0 deletions test/error.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# The next line is a deliberate error
sqrt(-3)
2 changes: 2 additions & 0 deletions test/noerror.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x = 5
sqrt(x)
29 changes: 27 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
using DebuggingUtilities
using Base.Test

# write your own tests here
@test 1 == 1
io = IOBuffer()
DebuggingUtilities.showlnio[] = io

function foo()
x = 5
@showln x
x = 7
@showln x
nothing
end

foo()

str = chomp(takebuf_string(io))
target = ("x = 5", "(in foo at", "x = 7", "(in foo at")
for (i,ln) in enumerate(split(str, '\n'))
ln = lstrip(ln)
@test startswith(ln, target[i])
end

DebuggingUtilities.showlnio[] = STDOUT
nothing

# Just make sure these run
test_showline("noerror.jl")
@test_throws DomainError test_showline("error.jl")
time_showline("noerror.jl")

7 comments on commit dbb621b

@Keno
Copy link

@Keno Keno commented on dbb621b Apr 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use @__LINE__ in @showln?

@timholy
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I forgot it existed.

@timholy
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually dates from julia-0.3, if not earlier.

@timholy
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, @__LINE__ seems to capture the line number of the macro. File contents:

macro line(ex)
    esc(quote
        println("Line number ", @__LINE__)
        $ex
    end)
end


function tst()
    @line x = 5
    x
end

Testing:

julia> include("/tmp/testline.jl")
tst (generic function with 1 method)

julia> tst()
Line number 3
5

julia> macroexpand(:(@line x = 5))
quote  # /tmp/testline.jl, line 3:
    println("Line number ",3) # /tmp/testline.jl, line 4:
    x = 5
end

Since you know a thing or two about backtraces, any ideas? It looks like @__LINE__ is implemented in julia-parser.scm.

@Keno
Copy link

@Keno Keno commented on dbb621b Apr 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct. It's magic and gets expanded upon parsing.

@timholy
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably this is solvable with a "wait to expand @__LINE__ until macro expansion is done" step, but I'll let @JeffBezanson decide whether he thinks that's worth it.

@timholy
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.