Beispiel #1
0
 def render(self, console: Console, options: ConsoleOptions) -> None:
     width = self.width or options.max_width or console.width
     options = options.update_dimensions(width, None)
     style = console.get_style(self.style)
     renderable = self.renderable
     if self.padding:
         renderable = Padding(renderable, self.padding)
     self._lines[:] = console.render_lines(renderable, options, style=style)
     self.size = Size(width, len(self._lines))
     self.page.emit_no_wait(PageUpdate(self.page))
Beispiel #2
0
 def __console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
     render_options = options.update(width=options.max_width - 4)
     lines = console.render_lines(self.elements, render_options, style=self.style)
     style = self.style
     new_line = Segment("\n")
     padding = Segment("▌ ", style)
     for line in lines:
         yield padding
         yield from line
         yield new_line
Beispiel #3
0
 def from_renderable(
     cls,
     console: Console,
     renderable: RenderableType,
     width: int,
     height: int,
 ) -> "LineCache":
     options = console.options.update_dimensions(width, height)
     lines = console.render_lines(renderable, options)
     return cls(lines)
    def render_bullet(self, console: Console, options: ConsoleOptions) -> RenderResult:
        render_options = options.update(width=options.max_width - 3)
        lines = console.render_lines(self.elements, render_options, style=self.style)
        bullet_style = console.get_style("markdown.item.bullet", default="none")

        bullet = Segment(" • ", bullet_style)
        padding = Segment(" " * 3, bullet_style)
        new_line = Segment("\n")
        for first, line in loop_first(lines):
            yield bullet if first else padding
            yield from line
            yield new_line
 def _render_horizontal(self, console: Console,
                        options: ConsoleOptions) -> RenderResult:
     render_widths = ratio_resolve(options.max_width, self.children)
     renders = [
         console.render_lines(child, options.update(width=render_width))
         for child, render_width in zip(self.children, render_widths)
     ]
     new_line = Segment.line()
     for lines in zip(*renders):
         for line in lines:
             yield from line
         yield new_line
 def _render_vertical(self, console: Console,
                      options: ConsoleOptions) -> RenderResult:
     render_heights = ratio_resolve(options.height or console.height,
                                    self.children)
     renders = [
         console.render_lines(child.renderable,
                              options.update(height=render_height),
                              new_lines=True)
         for child, render_height in zip(self.children, render_heights)
     ]
     for render in renders:
         for line in render:
             yield from line
    def __rich_console__(self, console: Console,
                         options: ConsoleOptions) -> RenderResult:
        render_options = options.update(
            height=options.height or self.height or options.size.height)
        if not self.children:
            for line in console.render_lines(self._renderable or "",
                                             render_options,
                                             new_lines=True):
                yield from line

        elif self.direction == "vertical":
            yield from self._render_vertical(console, render_options)
        elif self.direction == "horizontal":
            yield from self._render_horizontal(console, render_options)
    def render_number(
        self, console: Console, options: ConsoleOptions, number: int, last_number: int
    ) -> RenderResult:
        number_width = len(str(last_number)) + 2
        render_options = options.update(width=options.max_width - number_width)
        lines = console.render_lines(self.elements, render_options, style=self.style)
        number_style = console.get_style("markdown.item.number", default="none")

        new_line = Segment("\n")
        padding = Segment(" " * number_width, number_style)
        numeral = Segment(f"{number}".rjust(number_width - 1) + " ", number_style)
        for first, line in loop_first(lines):
            yield numeral if first else padding
            yield from line
            yield new_line
Beispiel #9
0
def test_format_table(response):
    table = format_table(response)
    assert isinstance(table, Table)

    def _get_id(value):
        return str(
            value.get('id') or value.get('record', {}).get('id') or value.get('taxon', {}).get('id')
        )

    # Just make sure at least object IDs show up in the table
    console = Console()
    rendered = '\n'.join([str(line) for line in console.render_lines(table)])

    if isinstance(response, list):
        assert all([_get_id(value) in rendered for value in response])
Beispiel #10
0
def test_render_size():
    console = Console(width=63, height=46, legacy_windows=False)
    options = console.options.update_dimensions(80, 4)
    lines = console.render_lines(Panel("foo", title="Hello"), options=options)
    print(repr(lines))
    expected = [
        [
            Segment("╭─", Style()),
            Segment(
                "────────────────────────────────── Hello ───────────────────────────────────"
            ),
            Segment("─╮", Style()),
        ],
        [
            Segment("│", Style()),
            Segment(" ", Style()),
            Segment("foo"),
            Segment(
                "                                                                         ",
                Style(),
            ),
            Segment(" ", Style()),
            Segment("│", Style()),
        ],
        [
            Segment("│", Style()),
            Segment(" ", Style()),
            Segment(
                "                                                                            ",
                Style(),
            ),
            Segment(" ", Style()),
            Segment("│", Style()),
        ],
        [
            Segment(
                "╰──────────────────────────────────────────────────────────────────────────────╯",
                Style(),
            )
        ],
    ]
    assert lines == expected
    def output_profiles(
        self,
        stats: ScaleneStatistics,
        pid: int,
        profile_this_code: Callable[[Filename, LineNumber], bool],
        python_alias_dir_name: Filename,
        python_alias_dir: Filename,
        profile_memory: bool = True,
        reduced_profile: bool = False,
    ) -> bool:
        """Write the profile out."""
        # Get the children's stats, if any.
        if not pid:
            stats.merge_stats(python_alias_dir_name)
            try:
                shutil.rmtree(python_alias_dir)
            except BaseException:
                pass
        current_max: float = stats.max_footprint
        # If we've collected any samples, dump them.
        if (not stats.total_cpu_samples
                and not stats.total_memory_malloc_samples
                and not stats.total_memory_free_samples):
            # Nothing to output.
            return False
        # Collect all instrumented filenames.
        all_instrumented_files: List[Filename] = list(
            set(
                list(stats.cpu_samples_python.keys()) +
                list(stats.cpu_samples_c.keys()) +
                list(stats.memory_free_samples.keys()) +
                list(stats.memory_malloc_samples.keys())))
        if not all_instrumented_files:
            # We didn't collect samples in source files.
            return False
        title = Text()
        mem_usage_line: Union[Text, str] = ""
        growth_rate = 0.0
        if profile_memory:
            samples = stats.memory_footprint_samples
            if len(samples.get()) > 0:
                # Output a sparkline as a summary of memory usage over time.
                _, _, spark_str = sparkline.generate(
                    samples.get()[0:samples.len()], 0, current_max)
                # Compute growth rate (slope), between 0 and 1.
                if stats.allocation_velocity[1] > 0:
                    growth_rate = (100.0 * stats.allocation_velocity[0] /
                                   stats.allocation_velocity[1])
                # If memory used is > 1GB, use GB as the unit.
                if current_max > 1024:
                    mem_usage_line = Text.assemble(
                        "Memory usage: ",
                        ((spark_str, "blue")),
                        (" (max: %6.2fGB, growth rate: %3.0f%%)\n" %
                         ((current_max / 1024), growth_rate)),
                    )
                else:
                    # Otherwise, use MB.
                    mem_usage_line = Text.assemble(
                        "Memory usage: ",
                        ((spark_str, "blue")),
                        (" (max: %6.2fMB, growth rate: %3.0f%%)\n" %
                         (current_max, growth_rate)),
                    )

        null = open("/dev/null", "w")
        # Get column width of the terminal and adjust to fit.
        # Note that Scalene works best with at least 132 columns.
        if self.html:
            column_width = 132
        else:
            column_width = shutil.get_terminal_size().columns
        console = Console(
            width=column_width,
            record=True,
            force_terminal=True,
            file=null,
        )
        # Build a list of files we will actually report on.
        report_files: List[Filename] = []
        # Sort in descending order of CPU cycles, and then ascending order by filename
        for fname in sorted(
                all_instrumented_files,
                key=lambda f: (-(stats.cpu_samples[f]), f),
        ):
            fname = Filename(fname)
            try:
                percent_cpu_time = (100 * stats.cpu_samples[fname] /
                                    stats.total_cpu_samples)
            except ZeroDivisionError:
                percent_cpu_time = 0

            # Ignore files responsible for less than some percent of execution time and fewer than a threshold # of mallocs.
            if (stats.malloc_samples[fname] < self.malloc_threshold
                    and percent_cpu_time < self.cpu_percent_threshold):
                continue
            report_files.append(fname)

        # Don't actually output the profile if we are a child process.
        # Instead, write info to disk for the main process to collect.
        if pid:
            stats.output_stats(pid, python_alias_dir_name)
            return True

        for fname in report_files:
            # Print header.
            percent_cpu_time = (100 * stats.cpu_samples[fname] /
                                stats.total_cpu_samples)
            new_title = mem_usage_line + (
                "%s: %% of time = %6.2f%% out of %6.2fs." %
                (fname, percent_cpu_time, stats.elapsed_time))
            # Only display total memory usage once.
            mem_usage_line = ""

            tbl = Table(
                box=box.MINIMAL_HEAVY_HEAD,
                title=new_title,
                collapse_padding=True,
                width=column_width - 1,
            )

            tbl.add_column("Line", justify="right", no_wrap=True)
            tbl.add_column("Time %\nPython", no_wrap=True)
            tbl.add_column("Time %\nnative", no_wrap=True)
            tbl.add_column("Sys\n%", no_wrap=True)
            tbl.add_column("GPU\n%", no_wrap=True)

            other_columns_width = 0  # Size taken up by all columns BUT code

            if profile_memory:
                tbl.add_column("Mem %\nPython", no_wrap=True)
                tbl.add_column("Net\n(MB)", no_wrap=True)
                tbl.add_column("Memory usage\nover time / %", no_wrap=True)
                tbl.add_column("Copy\n(MB/s)", no_wrap=True)
                other_columns_width = 72 + 5  # GPU
                tbl.add_column(
                    "\n" + fname,
                    width=column_width - other_columns_width,
                    no_wrap=True,
                )
            else:
                other_columns_width = 36 + 5  # GPU
                tbl.add_column(
                    "\n" + fname,
                    width=column_width - other_columns_width,
                    no_wrap=True,
                )

            # Print out the the profile for the source, line by line.
            with open(fname, "r") as source_file:
                # We track whether we should put in ellipsis (for reduced profiles)
                # or not.
                did_print = True  # did we print a profile line last time?
                code_lines = source_file.read()
                # Generate syntax highlighted version for the whole file,
                # which we will consume a line at a time.
                # See https://github.com/willmcgugan/rich/discussions/965#discussioncomment-314233
                syntax_highlighted = None
                if self.html:
                    syntax_highlighted = Syntax(
                        code_lines,
                        "python",
                        theme="default",
                        line_numbers=False,
                        code_width=None,
                    )
                else:
                    syntax_highlighted = Syntax(
                        code_lines,
                        "python",
                        theme="vim",
                        line_numbers=False,
                        code_width=None,
                    )
                capture_console = Console(
                    width=column_width - other_columns_width,
                    force_terminal=True,
                )
                formatted_lines = [
                    SyntaxLine(segments) for segments in
                    capture_console.render_lines(syntax_highlighted)
                ]
                for line_no, line in enumerate(formatted_lines, start=1):
                    old_did_print = did_print
                    did_print = self.output_profile_line(
                        fname,
                        LineNumber(line_no),
                        line,
                        console,
                        tbl,
                        stats,
                        profile_this_code,
                        profile_memory=profile_memory,
                        force_print=True,
                        suppress_lineno_print=False,
                        is_function_summary=False,
                        reduced_profile=reduced_profile,
                    )
                    if old_did_print and not did_print:
                        # We are skipping lines, so add an ellipsis.
                        tbl.add_row("...")
                    old_did_print = did_print

            # Potentially print a function summary.
            fn_stats = stats.build_function_stats(fname)
            print_fn_summary = False
            for fn_name in fn_stats.cpu_samples_python:
                if fn_name == fname:
                    continue
                print_fn_summary = True
                break

            if print_fn_summary:
                tbl.add_row(None, end_section=True)
                txt = Text.assemble("function summary", style="bold italic")
                if profile_memory:
                    tbl.add_row("", "", "", "", "", "", "", "", "", txt)
                else:
                    tbl.add_row("", "", "", "", "", txt)

                for fn_name in sorted(
                        fn_stats.cpu_samples_python,
                        key=lambda k: stats.firstline_map[k],
                ):
                    if fn_name == fname:
                        continue
                    if self.html:
                        syntax_highlighted = Syntax(
                            fn_name,
                            "python",
                            theme="default",
                            line_numbers=False,
                            code_width=None,
                        )
                    else:
                        syntax_highlighted = Syntax(
                            fn_name,
                            "python",
                            theme="vim",
                            line_numbers=False,
                            code_width=None,
                        )
                    # force print, suppress line numbers
                    self.output_profile_line(
                        fn_name,
                        LineNumber(1),
                        syntax_highlighted,  # type: ignore
                        console,
                        tbl,
                        fn_stats,
                        profile_this_code,
                        profile_memory=profile_memory,
                        force_print=True,
                        suppress_lineno_print=True,
                        is_function_summary=True,
                        reduced_profile=reduced_profile,
                    )

            console.print(tbl)

            # Report top K lines (currently 5) in terms of net memory consumption.
            net_mallocs: Dict[LineNumber, float] = defaultdict(float)
            for line_no in stats.bytei_map[fname]:
                for bytecode_index in stats.bytei_map[fname][line_no]:
                    net_mallocs[line_no] += (stats.memory_malloc_samples[fname]
                                             [line_no][bytecode_index] -
                                             stats.memory_free_samples[fname]
                                             [line_no][bytecode_index])
            net_mallocs = OrderedDict(
                sorted(net_mallocs.items(), key=itemgetter(1), reverse=True))
            if len(net_mallocs) > 0:
                console.print("Top net memory consumption, by line:")
                number = 1
                for net_malloc_lineno in net_mallocs:
                    if net_mallocs[net_malloc_lineno] <= 1:
                        break
                    if number > 5:
                        break
                    output_str = ("(" + str(number) + ") " +
                                  ("%5.0f" % (net_malloc_lineno)) + ": " +
                                  ("%5.0f" %
                                   (net_mallocs[net_malloc_lineno])) + " MB")
                    console.print(output_str)
                    number += 1

            # Only report potential leaks if the allocation velocity (growth rate) is above some threshold
            # FIXME: fixed at 1% for now.
            # We only report potential leaks where the confidence interval is quite tight and includes 1.
            growth_rate_threshold = 0.01
            leak_reporting_threshold = 0.05
            leaks = []
            if growth_rate / 100 > growth_rate_threshold:
                vec = list(stats.leak_score[fname].values())
                keys = list(stats.leak_score[fname].keys())
                for index, item in enumerate(stats.leak_score[fname].values()):
                    # See https://en.wikipedia.org/wiki/Rule_of_succession
                    frees = item[1]
                    allocs = item[0]
                    expected_leak = (frees + 1) / (frees + allocs + 2)
                    if expected_leak <= leak_reporting_threshold:
                        leaks.append((
                            keys[index],
                            1 - expected_leak,
                            net_mallocs[keys[index]],
                        ))
                if len(leaks) > 0:
                    # Report in descending order by least likelihood
                    for leak in sorted(leaks, key=itemgetter(1), reverse=True):
                        output_str = (
                            "Possible memory leak identified at line " +
                            str(leak[0]) + " (estimated likelihood: " +
                            ("%3.0f" %
                             (leak[1] * 100)) + "%" + ", velocity: " +
                            ("%3.0f MB/s" %
                             (leak[2] / stats.elapsed_time)) + ")")
                        console.print(output_str)

        if self.html:
            # Write HTML file.
            md = Markdown(
                "generated by the [scalene](https://github.com/plasma-umass/scalene) profiler"
            )
            console.print(md)
            if not self.output_file:
                self.output_file = "/dev/stdout"
            console.save_html(self.output_file, clear=False)
        else:
            if not self.output_file:
                # No output file specified: write to stdout.
                sys.stdout.write(console.export_text(styles=True))
            else:
                # Don't output styles to text file.
                console.save_text(self.output_file, styles=False, clear=False)
        return True
Beispiel #12
0
    def __rich_console__(self, console: Console,
                         options: ConsoleOptions) -> RenderResult:
        transparent_background = self._get_base_style().transaprent_background
        code_width = ((options.max_width - self._numbers_column_width -
                       1) if self.code_width is None else self.code_width)
        code = textwrap.dedent(self.code) if self.dedent else self.code
        code = code.expandtabs(self.tab_size)
        text = self.highlight(code)
        text.remove_suffix("\n")
        text.expand_tabs(self.tab_size)
        if not self.line_numbers:
            # Simple case of just rendering text
            yield from console.render(text,
                                      options=options.update(width=code_width))
            return

        lines = text.split("\n")
        line_offset = 0
        if self.line_range:
            start_line, end_line = self.line_range
            line_offset = max(0, start_line - 1)
            lines = lines[line_offset:end_line]

        numbers_column_width = self._numbers_column_width
        render_options = options.update(width=code_width)

        (
            background_style,
            number_style,
            highlight_number_style,
        ) = self._get_number_styles(console)

        highlight_line = self.highlight_lines.__contains__
        _Segment = Segment
        padding = _Segment(" " * numbers_column_width + " ", background_style)
        new_line = _Segment("\n")

        line_pointer = "❱ "

        for line_no, line in enumerate(lines, self.start_line + line_offset):
            if self.word_wrap:
                wrapped_lines = console.render_lines(
                    line,
                    render_options,
                    style=background_style,
                    pad=not transparent_background,
                )
            else:
                segments = list(line.render(console, end=""))
                if options.no_wrap:
                    wrapped_lines = [segments]
                else:
                    wrapped_lines = [
                        _Segment.adjust_line_length(
                            segments,
                            render_options.max_width,
                            style=background_style,
                            pad=not transparent_background,
                        )
                    ]
            for first, wrapped_line in loop_first(wrapped_lines):
                if first:
                    line_column = str(line_no).rjust(numbers_column_width -
                                                     2) + " "
                    if highlight_line(line_no):
                        yield _Segment(line_pointer, Style(color="red"))
                        yield _Segment(line_column, highlight_number_style)
                    else:
                        yield _Segment("  ", highlight_number_style)
                        yield _Segment(line_column, number_style)
                else:
                    yield padding
                yield from wrapped_line
                yield new_line
Beispiel #13
0
    def __rich_console__(self, console: Console,
                         options: ConsoleOptions) -> RenderResult:
        code_width = ((options.max_width - self._numbers_column_width -
                       1) if self.code_width is None else self.code_width)
        code = self.code
        if self.dedent:
            code = textwrap.dedent(code)
        text = self._highlight(self.lexer_name)
        if not self.line_numbers:
            if self.code_width is None:
                yield text
            else:
                yield from console.render(
                    text, options=options.update(width=code_width))
            return

        lines = text.split("\n")

        line_offset = 0
        if self.line_range:
            start_line, end_line = self.line_range
            line_offset = max(0, start_line - 1)
            lines = lines[line_offset:end_line]

        numbers_column_width = self._numbers_column_width
        render_options = options.update(width=code_width)

        (
            background_style,
            number_style,
            highlight_number_style,
        ) = self._get_number_styles(console)

        highlight_line = self.highlight_lines.__contains__
        _Segment = Segment
        padding = _Segment(" " * numbers_column_width + " ", background_style)
        new_line = _Segment("\n")

        line_pointer = "❱ "

        for line_no, line in enumerate(lines, self.start_line + line_offset):
            if self.word_wrap:
                wrapped_lines = console.render_lines(line,
                                                     render_options,
                                                     style=background_style)
            else:
                segments = list(line.render(console, end=""))
                wrapped_lines = [
                    Segment.adjust_line_length(segments,
                                               render_options.max_width,
                                               style=background_style)
                ]
            for first, wrapped_line in loop_first(wrapped_lines):
                if first:
                    line_column = str(line_no).rjust(numbers_column_width -
                                                     2) + " "
                    if highlight_line(line_no):
                        yield _Segment(line_pointer, number_style)
                        yield _Segment(
                            line_column,
                            highlight_number_style,
                        )
                    else:
                        yield _Segment("  ", highlight_number_style)
                        yield _Segment(
                            line_column,
                            number_style,
                        )
                else:
                    yield padding
                yield from wrapped_line
                yield new_line
Beispiel #14
0
    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:

        transparent_background = self._get_base_style().transparent_background
        code_width = (
            (
                (options.max_width - self._numbers_column_width - 1)
                if self.line_numbers
                else options.max_width
            )
            if self.code_width is None
            else self.code_width
        )

        ends_on_nl = self.code.endswith("\n")
        code = self.code if ends_on_nl else self.code + "\n"
        code = textwrap.dedent(code) if self.dedent else code
        code = code.expandtabs(self.tab_size)
        text = self.highlight(code, self.line_range)

        (
            background_style,
            number_style,
            highlight_number_style,
        ) = self._get_number_styles(console)

        if not self.line_numbers and not self.word_wrap and not self.line_range:
            if not ends_on_nl:
                text.remove_suffix("\n")
            # Simple case of just rendering text
            style = (
                self._get_base_style()
                + self._theme.get_style_for_token(Comment)
                + Style(dim=True)
                + self.background_style
            )
            if self.indent_guides and not options.ascii_only:
                text = text.with_indent_guides(self.tab_size, style=style)
                text.overflow = "crop"
            if style.transparent_background:
                yield from console.render(
                    text, options=options.update(width=code_width)
                )
            else:
                syntax_lines = console.render_lines(
                    text,
                    options.update(width=code_width, height=None, justify="left"),
                    style=self.background_style,
                    pad=True,
                    new_lines=True,
                )
                for syntax_line in syntax_lines:
                    yield from syntax_line
            return

        start_line, end_line = self.line_range or (None, None)
        line_offset = 0
        if start_line:
            line_offset = max(0, start_line - 1)
        lines: Union[List[Text], Lines] = text.split("\n", allow_blank=ends_on_nl)
        if self.line_range:
            lines = lines[line_offset:end_line]

        if self.indent_guides and not options.ascii_only:
            style = (
                self._get_base_style()
                + self._theme.get_style_for_token(Comment)
                + Style(dim=True)
                + self.background_style
            )
            lines = (
                Text("\n")
                .join(lines)
                .with_indent_guides(self.tab_size, style=style)
                .split("\n", allow_blank=True)
            )

        numbers_column_width = self._numbers_column_width
        render_options = options.update(width=code_width)

        highlight_line = self.highlight_lines.__contains__
        _Segment = Segment
        padding = _Segment(" " * numbers_column_width + " ", background_style)
        new_line = _Segment("\n")

        line_pointer = "> " if options.legacy_windows else "❱ "

        for line_no, line in enumerate(lines, self.start_line + line_offset):
            if self.word_wrap:
                wrapped_lines = console.render_lines(
                    line,
                    render_options.update(height=None, justify="left"),
                    style=background_style,
                    pad=not transparent_background,
                )

            else:
                segments = list(line.render(console, end=""))
                if options.no_wrap:
                    wrapped_lines = [segments]
                else:
                    wrapped_lines = [
                        _Segment.adjust_line_length(
                            segments,
                            render_options.max_width,
                            style=background_style,
                            pad=not transparent_background,
                        )
                    ]
            if self.line_numbers:
                for first, wrapped_line in loop_first(wrapped_lines):
                    if first:
                        line_column = str(line_no).rjust(numbers_column_width - 2) + " "
                        if highlight_line(line_no):
                            yield _Segment(line_pointer, Style(color="red"))
                            yield _Segment(line_column, highlight_number_style)
                        else:
                            yield _Segment("  ", highlight_number_style)
                            yield _Segment(line_column, number_style)
                    else:
                        yield padding
                    yield from wrapped_line
                    yield new_line
            else:
                for wrapped_line in wrapped_lines:
                    yield from wrapped_line
                    yield new_line
Beispiel #15
0
    def __init__(self, console: Console, renderable: RenderableType,
                 width: int, height: int) -> None:

        self.lines = console.render_lines(
            renderable, console.options.update_dimensions(width, height))
        self.offset = Offset(0, 0)