Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement table log monitor #8

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ version = "0.1.0"
[deps]
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"

[compat]
julia = "1"
Expand Down
23 changes: 20 additions & 3 deletions src/StickyMessages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ function StickyMessages(io::IO; ansi_codes=io isa Base.TTY &&
sticky
end

function firstline!(sticky::StickyMessages, label)
idx = findfirst(m -> m[1] == label, sticky.messages)
idx === nothing && throw(KeyError(label))
idx == 1 && return
sticky.messages[idx], sticky.messages[1] = sticky.messages[1], sticky.messages[idx]
return
end

# Count newlines in a message or sequence of messages
_countlines(msg::String) = sum(c->c=='\n', msg)
_countlines(messages) = length(messages) > 0 ? sum(_countlines, messages) : 0
Expand Down Expand Up @@ -93,7 +101,7 @@ function showsticky(io, prev_nlines, messages)
nothing
end

function Base.push!(sticky::StickyMessages, message::Pair)
function Base.push!(sticky::StickyMessages, message::Pair; first=true)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Looking at this again now, I realize that it'd be better to overload push! and pushfirst!.

if !sticky.ansi_codes
write(sticky.io, message[2])
return
Expand All @@ -103,9 +111,18 @@ function Base.push!(sticky::StickyMessages, message::Pair)
prev_nlines = _countlines(sticky.messages)
idx = findfirst(m->m[1] == label, sticky.messages)
if idx === nothing
push!(sticky.messages, label=>text)
if first
insert!(sticky.messages, 1, label=>text)
else
push!(sticky.messages, label=>text)
end
else
sticky.messages[idx] = label=>text
if first
sticky.messages[idx] = sticky.messages[1]
sticky.messages[1] = label=>text
else
sticky.messages[idx] = label=>text
end
end
showsticky(sticky.io, prev_nlines, sticky.messages)
end
Expand Down
108 changes: 108 additions & 0 deletions src/TableMonitor.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
struct Row
id
names::Vector{String}
values::Vector{String}
end

struct TableMonitor
sticky_messages::StickyMessages
io::IO
rows::Vector{Row}
flushed::Vector{Bool}
end

TableMonitor(sticky_messages::StickyMessages, io::IO = sticky_messages.io) =
TableMonitor(sticky_messages, io, [], [])

function tablelines(
table::TableMonitor,
oldrows = nothing,
tobeflushed = ();
bottom_line = false,
)
data = permutedims(mapreduce(row -> row.values, vcat, table.rows))
highlighters = ()
if oldrows !== nothing
data = vcat(data, permutedims(mapreduce(row -> row.values, vcat, oldrows)))
right = cumsum(Int[length(row.values) for row in oldrows])
left = insert!(right[1:end-1], 1, 0)
# Dim unchanged rows:
highlighters = Highlighter(
(data, i, j) -> i == 2 && !any(k -> left[k] < j <= right[k], tobeflushed);
foreground = :dark_gray,
)
end
text = sprint(context = table.io) do io
pretty_table(
io,
data,
mapreduce(row -> row.names, vcat, table.rows),
PrettyTableFormat(top_line = false, bottom_line = bottom_line);
highlighters = highlighters,
)
end
return split(text, "\n")
end

function draw!(table::TableMonitor, oldrows = nothing, tobeflushed = ())
lines = tablelines(table, oldrows, tobeflushed)
formatted = join(reverse!(lines[1:3]), "\n") # row, hline, header
if oldrows !== nothing
print(table.io, '\n', lines[4])
end
push!(table.sticky_messages, _tablelabel => formatted; first = true)
end

const _tablelabel = gensym("TableMonitor")

Base.push!(table::TableMonitor, (id, namedtuple)::Pair{<:Any,<:NamedTuple}) = push!(
table,
Row(id, collect(string.(keys(namedtuple))), collect(string.(values(namedtuple)))),
)

function Base.push!(table::TableMonitor, row::Row)
idx = findfirst(x -> x.id == row.id, table.rows)
if idx !== nothing
if table.flushed[idx]
tobeflushed = ()
oldrows = nothing
table.flushed[idx] = false
else
tobeflushed = findall(!, table.flushed)
oldrows = copy(table.rows)
table.flushed .= [i != idx for i in 1:length(table.rows)]
end
table.rows[idx] = row
draw!(table, oldrows, tobeflushed)
else
push!(table.flushed, false)
push!(table.rows, row)
draw!(table)
end
return table
end

function flush_table!(table::TableMonitor)
pop!(table.sticky_messages, _tablelabel)

lines = tablelines(table; bottom_line = true)[1:end-1]
bottom = pop!(lines)
for line in reverse!(lines)
print(table.io, '\n', line)
end
print(table.io, '\n', bottom)

fill!(table.flushed, true)
end

function Base.pop!(table::TableMonitor, id)
idx = findfirst(x -> x.id == id, table.rows)
idx === nothing && return

if !table.flushed[idx]
flush_table!(table)
end

deleteat!(table.flushed, idx)
return deleteat!(table.rows, idx)
end
40 changes: 39 additions & 1 deletion src/TerminalLogger.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,24 @@ struct TerminalLogger <: AbstractLogger
message_limits::Dict{Any,Int}
sticky_messages::StickyMessages
bars::Dict{Any,ProgressBar}
was_table::Base.RefValue{Bool}
table::TableMonitor
end
function TerminalLogger(stream::IO=stderr, min_level=ProgressLevel;
meta_formatter=default_metafmt, show_limited=true,
right_justify=0)
sticky_messages = StickyMessages(stream)
TerminalLogger(
stream,
min_level,
meta_formatter,
show_limited,
right_justify,
Dict{Any,Int}(),
StickyMessages(stream),
sticky_messages,
Dict{Any,ProgressBar}(),
Ref(false),
TableMonitor(sticky_messages),
)
end

Expand Down Expand Up @@ -130,6 +135,7 @@ function handle_progress(logger, message, id, progress)
)

if progress == "done" || progress >= 1
flush_table(logger)
pop!(logger.sticky_messages, id)
println(logger.stream, bartxt)
else
Expand All @@ -143,6 +149,36 @@ function handle_progress(logger, message, id, progress)
end
end

struct Unspecified end

function maybe_handle_table(logger, id; table = Unspecified(), _...)
if table isa NamedTuple
push!(logger.table, id => table)
logger.was_table[] = true
return true
elseif table === nothing
pop!(logger.table, id)
logger.was_table[] = true
return true
end
flush_table(logger)
return false
end

function flush_table(logger)
if logger.was_table[]
flush_table!(logger.table)

# To "connect" flushed rows with the current row and header in
# the sticky messages, `TableMonitor` do not print newline
# after the message. Thus, we need to print a newline when
# switching to non-table message:
println(logger.stream)

logger.was_table[] = false
end
end

function handle_message(logger::TerminalLogger, level, message, _module, group, id,
filepath, line; maxlog=nothing, progress=nothing,
sticky=nothing, kwargs...)
Expand All @@ -157,6 +193,8 @@ function handle_message(logger::TerminalLogger, level, message, _module, group,
return
end

maybe_handle_table(logger, id; kwargs...) && return

substr(s) = SubString(s, 1, length(s)) # julia 0.6 compat

# Generate a text representation of the message and all key value pairs,
Expand Down
4 changes: 4 additions & 0 deletions src/TerminalLoggers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ using Logging:
import Logging:
handle_message, shouldlog, min_enabled_level, catch_exceptions

using PrettyTables:
Highlighter, PrettyTableFormat, pretty_table

export TerminalLogger

const ProgressLevel = LogLevel(-1)
Expand All @@ -16,6 +19,7 @@ using .ProgressMeter:
ProgressBar, printprogress

include("StickyMessages.jl")
include("TableMonitor.jl")
include("TerminalLogger.jl")

end # module