Skip to content

Commit 8322d79

Browse files
committed
print memory usage and a truncated version of the object in whos()
fix #3393. close #7603. close #11461.
1 parent e20f4c0 commit 8322d79

File tree

4 files changed

+259
-13
lines changed

4 files changed

+259
-13
lines changed

base/interactiveutil.jl

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,3 +409,242 @@ function runtests(tests = ["all"], numcores = ceil(Int,CPU_CORES/2))
409409
"including error messages above and the output of versioninfo():\n$(readall(buf))")
410410
end
411411
end
412+
413+
# testing
414+
415+
function whos(io::IO=STDOUT, m::Module=current_module(), pattern::Regex=r"")
416+
maxline = tty_size()[2]
417+
line = zeros(UInt8, maxline)
418+
head = PipeBuffer(maxline + 1)
419+
for v in sort!(names(m))
420+
s = string(v)
421+
if isdefined(m, v) && ismatch(pattern, s)
422+
value = getfield(m, v)
423+
bytes = summarysize(value, true)
424+
@printf head "%30s " s
425+
if bytes < 10_000
426+
@printf(head, "%6d bytes ", bytes)
427+
else
428+
@printf(head, "%6d KB ", bytes ÷ (1024))
429+
end
430+
print(head, summary(value))
431+
print(head, " : ")
432+
show(head, value)
433+
434+
newline = search(head, UInt8('\n')) - 1
435+
if newline < 0
436+
newline = nb_available(head)
437+
end
438+
if newline > maxline
439+
newline = maxline - 1 # make space for ...
440+
end
441+
line = resize!(line, newline)
442+
line = read!(head, line)
443+
444+
write(io, line)
445+
if nb_available(head) > 0 # more to read? replace with ...
446+
print(io, '\u2026') # hdots
447+
end
448+
println(io)
449+
seekend(head) # skip the rest of the text
450+
end
451+
end
452+
end
453+
whos(m::Module, pat::Regex=r"") = whos(STDOUT, m, pat)
454+
whos(pat::Regex) = whos(STDOUT, current_module(), pat)
455+
456+
# summarysize is an estimate of the size of the object
457+
# as if all iterables were allocated inline
458+
# in general, this forms a conservative lower bound
459+
# on the memory "controlled" by the object
460+
# if recurse is true, then simply reachable memory
461+
# should also be included, otherwise, only
462+
# directly used memory should be included
463+
# you should never ignore recurse in cases where recursion is possible
464+
465+
summarysize(obj::ANY, recurse::Bool) = try convert(Int, sizeof(obj)); catch; Core.sizeof(obj); end
466+
467+
# these three cases override the exception that would be thrown by Core.sizeof
468+
summarysize(obj::Symbol, recurse::Bool) = 0
469+
summarysize(obj::DataType, recurse::Bool) = 0
470+
function summarysize(obj::Module, recurse::Bool)
471+
size::Int = sizeof(obj)
472+
if recurse
473+
for binding in names(obj, true)
474+
if isdefined(obj, binding)
475+
value = getfield(obj, binding)
476+
if (value !== obj) # skip the self-recursive definition
477+
recurseok = !isa(value, Module) || module_parent(value) === obj
478+
size += summarysize(value, recurseok)::Int # recurse on anything that isn't a module
479+
end
480+
end
481+
end
482+
end
483+
return size
484+
end
485+
486+
function summarysize(obj::Task, recurse::Bool)
487+
size::Int = sizeof(obj)
488+
if recurse
489+
size += summarysize(obj.code, true)::Int
490+
size += summarysize(obj.storage, true)::Int
491+
492+
size += summarysize(obj.backtrace, false)::Int
493+
size += summarysize(obj.donenotify, false)::Int
494+
size += summarysize(obj.exception, false)::Int
495+
size += summarysize(obj.result, false)::Int
496+
end
497+
return size
498+
end
499+
500+
function summarysize(obj::SimpleVector, recurse::Bool)
501+
size::Int = sizeof(obj)
502+
if recurse
503+
for val in obj
504+
if val !== obj
505+
size += summarysize(val, false)::Int
506+
end
507+
end
508+
end
509+
return size
510+
end
511+
512+
function summarysize(obj::Tuple, recurse::Bool)
513+
size::Int = sizeof(obj)
514+
if recurse
515+
for val in obj
516+
if val !== obj && !isbits(val)
517+
size += summarysize(val, recurse)::Int
518+
end
519+
end
520+
end
521+
return size
522+
end
523+
524+
function summarysize(obj::Array, recurse::Bool)
525+
size::Int = sizeof(obj)
526+
if recurse && !isbits(eltype(obj))
527+
for i in 1:length(obj)
528+
if isdefined(obj, i) && (val = obj[i]) !== obj
529+
size += summarysize(val, false)::Int
530+
end
531+
end
532+
end
533+
return size
534+
end
535+
536+
function summarysize(obj::AbstractArray, recurse::Bool)
537+
size::Int = sizeof(obj)
538+
if recurse && !isbits(eltype(obj))
539+
for val in obj
540+
if val !== obj
541+
size += summarysize(val, false)::Int
542+
end
543+
end
544+
end
545+
return size
546+
end
547+
548+
function summarysize(obj::Associative, recurse::Bool)
549+
size::Int = sizeof(obj)
550+
if recurse
551+
for (key, val) in obj
552+
if key !== obj
553+
size += summarysize(key, false)::Int
554+
end
555+
if val !== obj
556+
size += summarysize(val, false)::Int
557+
end
558+
end
559+
end
560+
return size
561+
end
562+
563+
function summarysize(obj::Dict, recurse::Bool)
564+
size::Int = sizeof(obj)
565+
size += summarysize(obj.keys, recurse)::Int
566+
size += summarysize(obj.vals, recurse)::Int
567+
size += summarysize(obj.slots, recurse)::Int
568+
return size
569+
end
570+
571+
summarysize(obj::Set, recurse::Bool) =
572+
sizeof(obj) + summarysize(obj.dict, recurse)
573+
574+
function summarysize(obj::Function, recurse::Bool)
575+
size::Int = sizeof(obj)
576+
size += summarysize(obj.env, recurse)::Int
577+
if isdefined(obj, :code)
578+
size += summarysize(obj.code, recurse)::Int
579+
end
580+
return size
581+
end
582+
583+
function summarysize(obj::MethodTable, recurse::Bool)
584+
size::Int = sizeof(obj)
585+
size += summarysize(obj.defs, recurse)::Int
586+
size += summarysize(obj.cache, recurse)::Int
587+
size += summarysize(obj.cache_arg1, recurse)::Int
588+
size += summarysize(obj.cache_targ, recurse)::Int
589+
if isdefined(obj, :kwsorter)
590+
size += summarysize(obj.kwsorter, recurse)::Int
591+
end
592+
return size
593+
end
594+
595+
function summarysize(obj::Method, recurse::Bool)
596+
size::Int = sizeof(obj)
597+
size += summarysize(obj.func, recurse)::Int
598+
size += summarysize(obj.next, recurse)::Int
599+
return size
600+
end
601+
602+
function summarysize(obj::LambdaStaticData, recurse::Bool)
603+
size::Int = sizeof(obj)
604+
size += summarysize(obj.ast, true)::Int # always include the AST
605+
size += summarysize(obj.sparams, true)::Int
606+
if isdefined(obj, :roots)
607+
size += summarysize(obj.roots, recurse)::Int
608+
end
609+
if isdefined(obj, :capt)
610+
size += summarysize(obj.capt, false)::Int
611+
end
612+
return size
613+
end
614+
615+
function summarysize(obj::Expr, recurse::Bool)
616+
size::Int = sizeof(obj) + sizeof(obj.args)
617+
if recurse
618+
for arg in obj.args
619+
size += summarysize(arg, isa(arg, Expr))::Int
620+
end
621+
end
622+
return size
623+
end
624+
625+
function summarysize(obj::Box, recurse::Bool)
626+
size::Int = sizeof(obj)
627+
# ignore the recurse parameter for Box,
628+
# even though it could in theory recurse
629+
# since it is an internal construct
630+
# used by codegen with very limited usage
631+
if isdefined(obj, :contents)
632+
if obj.contents !== obj
633+
size += summarysize(obj.contents, false)::Int
634+
end
635+
end
636+
return size
637+
end
638+
639+
function summarysize(obj::Ref, recurse::Bool)
640+
size::Int = sizeof(obj)
641+
if recurse && !isbits(eltype(obj))
642+
try
643+
val = obj[]
644+
if val !== obj
645+
size += summarysize(val, false)::Int
646+
end
647+
end
648+
end
649+
return size
650+
end

base/iobuffer.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,8 @@ readbytes(io::AbstractIOBuffer, nb) = read!(io, Array(UInt8, min(nb, nb_availabl
343343
function search(buf::IOBuffer, delim::UInt8)
344344
p = pointer(buf.data, buf.ptr)
345345
q = ccall(:memchr,Ptr{UInt8},(Ptr{UInt8},Int32,Csize_t),p,delim,nb_available(buf))
346-
nb = (q == C_NULL ? 0 : q-p+1)
346+
nb::Int = (q == C_NULL ? 0 : q-p+1)
347+
return nb
347348
end
348349

349350
function search(buf::AbstractIOBuffer, delim::UInt8)

base/show.jl

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,18 +1166,6 @@ function show_nd(io::IO, a::AbstractArray, limit, print_matrix, label_slices)
11661166
cartesianmap(print_slice, tail)
11671167
end
11681168

1169-
function whos(m::Module, pattern::Regex)
1170-
for v in sort(names(m))
1171-
s = string(v)
1172-
if isdefined(m,v) && ismatch(pattern, s)
1173-
println(rpad(s, 30), summary(eval(m,v)))
1174-
end
1175-
end
1176-
end
1177-
whos() = whos(r"")
1178-
whos(m::Module) = whos(m, r"")
1179-
whos(pat::Regex) = whos(current_module(), pat)
1180-
11811169
# global flag for limiting output
11821170
# TODO: this should be replaced with a better mechanism. currently it is only
11831171
# for internal use in showing arrays.

test/misc.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,21 @@ v11801, t11801 = @timed sin(1)
156156
@test isa(t11801,Real) && t11801 >= 0
157157

158158
@test names(current_module(), true) == names_before_timing
159+
160+
# interactive utilities
161+
162+
import Base.summarysize
163+
@test summarysize(Core, true) > summarysize(Core.Inference, true) > summarysize(Core, false) == summarysize(Core.Inference, false) == Core.sizeof(Core)
164+
@test summarysize(Main, true) > 10_000*summarysize(Main, false) > 10_000*sizeof(Int)
165+
@test 0 == summarysize(Int, true) == summarysize(Int, false) == summarysize(DataType, true) == summarysize(Ptr, true) == summarysize(Any, true)
166+
@test sprint(whos, Main, r"^$") == ""
167+
let v = sprint(whos, Main)
168+
@test contains(v, " KB Module : Main")
169+
@test contains(v, " KB Module : Base")
170+
@test contains(v, " KB Module : Core")
171+
@test contains(v, " 0 bytes DataType : NoMethodHasThisType")
172+
@test contains(v, "\u2026\n")
173+
@test match(r".\u2026$"m, v) !== nothing
174+
@test match(r"\u2026."m, v) === nothing
175+
@test !contains(v, "Core.Inference")
176+
end

0 commit comments

Comments
 (0)