pulumi/pkg/backend/display/tree.go

472 lines
12 KiB
Go
Raw Permalink Normal View History

[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
// Copyright 2016-2022, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package display
import (
"errors"
"fmt"
"io"
"strings"
"sync"
"time"
"unicode/utf8"
"github.com/pkg/browser"
"github.com/pulumi/pulumi/pkg/v3/backend/display/internal/terminal"
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
"github.com/pulumi/pulumi/pkg/v3/engine"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)
type treeRenderer struct {
m sync.Mutex
opts Options
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
display *ProgressDisplay
term terminal.Terminal
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
permalink string
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
dirty bool // True if the display has changed since the last redraw.
rewind int // The number of lines we need to rewind to redraw the entire screen.
treeTableRows []string
systemMessages []string
statusMessage string
statusMessageDeadline time.Time
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
ticker *time.Ticker
keys chan string
closed chan bool
treeTableOffset int // The scroll offset into the tree table.
maxTreeTableOffset int // The maximum scroll offset.
}
func newInteractiveRenderer(term terminal.Terminal, permalink string, opts Options) progressRenderer {
// Something about the tree renderer--possibly the raw terminal--does not yet play well with Windows, so for now
// we fall back to the legacy renderer on that platform.
if !term.IsRaw() {
return newInteractiveMessageRenderer(term, opts)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
term.HideCursor()
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
r := &treeRenderer{
opts: opts,
term: term,
permalink: permalink,
ticker: time.NewTicker(16 * time.Millisecond),
keys: make(chan string),
closed: make(chan bool),
}
Add display to the engine tests (#16050) We want to add more test coverage to the display code. The best way to do that is to add it to the engine tests, that already cover most of the pulumi functionality. It's probably not really possible to review all of the output, but at least it gives us a baseline, which we can work with. There's a couple of tests that are flaky for reasons I don't quite understand yet. I marked them as to skip and we can look at them later. I'd rather get in the baseline tests sooner, rather than spending a bunch of time looking at that. The output differences also seem very minor, so not super concerning. The biggest remaining issue is that this doesn't interact well with the Chdir we're doing in the engine. We could either pass the CWD through, or just try to get rid of that Chdir. So this should only be merged after https://github.com/pulumi/pulumi/pull/15607. I've tried to split this into a few commits, separating out adding the testdata, so it's hopefully a little easier to review, even though the PR is still quite large. One other thing to note is that we're comparing that the output has all the same lines, and not that it is exactly the same. Because of how the engine is implemented, there's a bunch of race conditions otherwise, that would make us have to skip a bunch of tests, just because e.g. resource A is sometimes deleted before resource B and sometimes it's the other way around. The biggest downside of that is that running with `PULUMI_ACCEPT` will produce a diff even when there are no changes. Hopefully we won't have to run that way too often though, so it might not be a huge issue? --------- Co-authored-by: Fraser Waters <fraser@pulumi.com>
2024-05-13 07:18:25 +00:00
if opts.DeterministicOutput {
r.ticker.Stop()
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
go r.handleEvents()
go r.pollInput()
return r
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
func (r *treeRenderer) Close() error {
r.term.ShowCursor()
return r.term.Close()
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
func (r *treeRenderer) initializeDisplay(display *ProgressDisplay) {
r.display = display
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
func (r *treeRenderer) tick() {
r.markDirty()
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
func (r *treeRenderer) rowUpdated(_ Row) {
r.markDirty()
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
func (r *treeRenderer) systemMessage(_ engine.StdoutEventPayload) {
r.markDirty()
}
func (r *treeRenderer) done() {
r.markDirty()
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
r.ticker.Stop()
r.closed <- true
close(r.closed)
r.frame(false, true)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
func (r *treeRenderer) showStatusMessage(msg string, duration time.Duration) {
r.m.Lock()
defer r.m.Unlock()
r.statusMessage, r.statusMessageDeadline = msg, time.Now().Add(duration)
}
func (r *treeRenderer) print(text string) {
_, err := r.term.Write([]byte(r.opts.Color.Colorize(text)))
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
contract.IgnoreError(err)
}
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
func (r *treeRenderer) println(text string) {
r.print(text)
r.print("\n")
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
func (r *treeRenderer) over(text string) {
r.print(text)
r.term.ClearEnd()
}
func (r *treeRenderer) overln(text string) {
r.over(text)
r.print("\n")
}
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
func (r *treeRenderer) render() {
contract.Assertf(!r.m.TryLock(), "treeRenderer.render() MUST be called from within a locked context")
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
if r.display.headerRow == nil {
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
return
}
// Render the resource tree table into rows.
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
rootNodes := r.display.generateTreeNodes()
rootNodes = r.display.filterOutUnnecessaryNodesAndSetDisplayTimes(rootNodes)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
sortNodes(rootNodes)
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
r.display.addIndentations(rootNodes, true /*isRoot*/, "")
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
maxSuffixLength := 0
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
for _, v := range r.display.suffixesArray {
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
runeCount := utf8.RuneCountInString(v)
if runeCount > maxSuffixLength {
maxSuffixLength = runeCount
}
}
var treeTableRows [][]string
var maxColumnLengths []int
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
r.display.convertNodesToRows(rootNodes, maxSuffixLength, &treeTableRows, &maxColumnLengths)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
removeInfoColumnIfUnneeded(treeTableRows)
r.treeTableRows = r.treeTableRows[:0]
for _, row := range treeTableRows {
rendered := renderRow(row, maxColumnLengths)
r.treeTableRows = append(r.treeTableRows, rendered)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
// Convert system events into lines.
r.systemMessages = r.systemMessages[:0]
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
for _, payload := range r.display.systemEventPayloads {
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
msg := payload.Color.Colorize(payload.Message)
r.systemMessages = append(r.systemMessages, splitIntoDisplayableLines(msg)...)
}
}
func (r *treeRenderer) markDirty() {
r.m.Lock()
defer r.m.Unlock()
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
if r.display == nil || r.display.headerRow == nil {
// Don't mark dirty if there is no display, or
// if the display has never been initialized
return
}
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
r.dirty = true
Add display to the engine tests (#16050) We want to add more test coverage to the display code. The best way to do that is to add it to the engine tests, that already cover most of the pulumi functionality. It's probably not really possible to review all of the output, but at least it gives us a baseline, which we can work with. There's a couple of tests that are flaky for reasons I don't quite understand yet. I marked them as to skip and we can look at them later. I'd rather get in the baseline tests sooner, rather than spending a bunch of time looking at that. The output differences also seem very minor, so not super concerning. The biggest remaining issue is that this doesn't interact well with the Chdir we're doing in the engine. We could either pass the CWD through, or just try to get rid of that Chdir. So this should only be merged after https://github.com/pulumi/pulumi/pull/15607. I've tried to split this into a few commits, separating out adding the testdata, so it's hopefully a little easier to review, even though the PR is still quite large. One other thing to note is that we're comparing that the output has all the same lines, and not that it is exactly the same. Because of how the engine is implemented, there's a bunch of race conditions otherwise, that would make us have to skip a bunch of tests, just because e.g. resource A is sometimes deleted before resource B and sometimes it's the other way around. The biggest downside of that is that running with `PULUMI_ACCEPT` will produce a diff even when there are no changes. Hopefully we won't have to run that way too often though, so it might not be a huge issue? --------- Co-authored-by: Fraser Waters <fraser@pulumi.com>
2024-05-13 07:18:25 +00:00
if r.opts.DeterministicOutput {
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
r.frame(true, false)
}
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
// +--------------------------------------------+
// | treetable header |
// | treetable contents... |
// | treetable footer |
// | system messages header |
// | system messages contents... |
// | status message |
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
// +--------------------------------------------+
func (r *treeRenderer) frame(locked, done bool) {
if !locked {
r.m.Lock()
defer r.m.Unlock()
}
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
if !done && !r.dirty {
return
}
r.dirty = false
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
contract.Assertf(r.display != nil, "treeRender.initializeDisplay MUST be called before rendering")
r.render()
termWidth, termHeight, err := r.term.Size()
contract.IgnoreError(err)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
treeTableRows := r.treeTableRows
systemMessages := r.systemMessages
statusMessage := r.statusMessage
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
var treeTableHeight int
var treeTableHeader string
if len(r.treeTableRows) > 0 {
treeTableHeader, treeTableRows = treeTableRows[0], treeTableRows[1:]
treeTableHeight = 1 + len(treeTableRows)
}
systemMessagesHeight := len(systemMessages)
if len(systemMessages) > 0 {
systemMessagesHeight += 3 // Account for padding + title
}
statusMessageHeight := 0
if !done && r.statusMessage != "" {
statusMessageHeight = 1
}
// Enable autoscrolling if the display is scrolled to its maximum offset.
autoscroll := r.treeTableOffset == r.maxTreeTableOffset
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
// Layout the display. The extra '1' accounts for the fact that we terminate each line with a newline.
totalHeight := treeTableHeight + systemMessagesHeight + statusMessageHeight + 1
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
r.maxTreeTableOffset = 0
// If this is not the final frame and the terminal is not large enough to show the entire display:
// - If there are no system messages, devote the entire display to the tree table
// - If there are system messages, devote the first two thirds of the display to the tree table and the
// last third to the system messages
var treeTableFooter string
if !done && totalHeight >= termHeight {
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
if systemMessagesHeight > 0 {
systemMessagesHeight = termHeight / 3
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
if systemMessagesHeight <= 3 {
systemMessagesHeight = 0
} else {
systemMessagesContentHeight := systemMessagesHeight - 3
if len(systemMessages) > systemMessagesContentHeight {
systemMessages = systemMessages[len(systemMessages)-systemMessagesContentHeight:]
}
}
}
// If there are no system messages and we have a status message to display, fold the status message into the
// last line of the tree table (where the scroll indicator is displayed).
mergeLastLine := systemMessagesHeight == 0 && statusMessageHeight != 0
treeTableHeight = termHeight - systemMessagesHeight - statusMessageHeight - 1
r.maxTreeTableOffset = len(treeTableRows) - treeTableHeight + 1
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
if r.maxTreeTableOffset < 0 {
r.maxTreeTableOffset = 0
}
scrollable := r.maxTreeTableOffset != 0
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
if r.treeTableOffset > r.maxTreeTableOffset {
r.treeTableOffset = r.maxTreeTableOffset
}
if autoscroll {
r.treeTableOffset = r.maxTreeTableOffset
}
if treeTableHeight <= 0 {
// Ensure that the treeTableHeight is at least 1 to avoid going out of bounds.
treeTableHeight = 1
}
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
if r.treeTableOffset+treeTableHeight-1 < len(treeTableRows) {
treeTableRows = treeTableRows[r.treeTableOffset : r.treeTableOffset+treeTableHeight-1]
} else if r.treeTableOffset < len(treeTableRows) {
treeTableRows = treeTableRows[r.treeTableOffset:]
}
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
totalHeight = treeTableHeight + systemMessagesHeight + statusMessageHeight + 1
footer := ""
if scrollable {
upArrow := " "
if r.treeTableOffset != 0 {
upArrow = "⬆ "
}
downArrow := " "
if r.treeTableOffset != r.maxTreeTableOffset {
downArrow = "⬇ "
}
footer = colors.BrightBlue + fmt.Sprintf("%smore%s", upArrow, downArrow) + colors.Reset
}
padding := termWidth - colors.MeasureColorizedString(footer)
// Combine any last-line content.
prefix := ""
if mergeLastLine {
prefix = r.clampLine(statusMessage, padding-1) + " "
padding -= colors.MeasureColorizedString(prefix)
statusMessageHeight, statusMessage = 0, ""
}
if padding < 0 {
// Padding can potentially go negative on very small terminals.
// This will cause a panic. To avoid this, we clamp the padding to 0.
// The user won't be able to see anything anyway.
padding = 0
}
treeTableFooter = r.opts.Color.Colorize(prefix + strings.Repeat(" ", padding) + footer)
if systemMessagesHeight > 0 {
treeTableFooter += "\n"
}
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
// Re-home the cursor.
r.print("\r")
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
for ; r.rewind > 0; r.rewind-- {
// If there is content that we won't overwrite, clear it.
if r.rewind > totalHeight-1 {
r.term.ClearEnd()
}
r.term.CursorUp(1)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
r.rewind = totalHeight - 1
// Render the tree table.
r.overln(r.clampLine(treeTableHeader, termWidth))
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
for _, row := range treeTableRows {
r.overln(r.clampLine(row, termWidth))
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
if treeTableFooter != "" {
r.over(treeTableFooter)
}
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
// Render the system messages.
if systemMessagesHeight > 0 {
r.overln("")
r.overln(colors.Yellow + "System Messages" + colors.Reset)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
for _, line := range systemMessages {
r.overln(" " + line)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
}
// Render the status message, if any.
if statusMessageHeight != 0 {
padding := termWidth - colors.MeasureColorizedString(statusMessage)
r.overln("")
r.over(statusMessage + strings.Repeat(" ", padding))
}
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
if done && totalHeight > 0 {
r.overln("")
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
// Handle the status message timer. We do this at the end to ensure that any message is displayed for at least one
// frame.
if !r.statusMessageDeadline.IsZero() && r.statusMessageDeadline.Before(time.Now()) {
r.statusMessage, r.statusMessageDeadline = "", time.Time{}
}
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
func (r *treeRenderer) clampLine(line string, maxWidth int) string {
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
// Ensure we don't go past the end of the terminal. Note: this is made complex due to
// msgWithColors having the color code information embedded with it. So we need to get
// the right substring of it, assuming that embedded colors are just markup and do not
// actually contribute to the length
maxRowLength := maxWidth - 1
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
if maxRowLength < 0 {
maxRowLength = 0
}
return colors.TrimColorizedString(line, maxRowLength)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
}
func (r *treeRenderer) handleEvents() {
for {
select {
case <-r.ticker.C:
r.frame(false, false)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
case key := <-r.keys:
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
r.handleKey(key)
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
case <-r.closed:
return
}
}
}
Decouple persist and display events (#15709) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description Retry #15529 with fix for the issue that required the revert in #15705 This removes a scenario where events could not be persisted to the cloud because they were waiting on the same event being displayed Instead of rendering the tree every time a row is updated, instead, this renders when the display actually happens in the the `frame` call. The renderer instead simply marks itself as dirty in the `rowUpdated`, `tick`, `systemMessage` and `done` methods and relies on the frame being redrawn on a 60Hz timer (the `done` method calls `frame` explicitly). This makes the rowUpdated call exceedingly cheap (it simply marks the treeRenderer as dirty) which allows the ProgressDisplay instance to service the display events faster, which prevents it from blocking the persist events. This requires a minor refactor to ensure that the display object is available in the frame method Because the treeRenderer is calling back into the ProgressDisplay object in a goroutine, the ProgressDisplay object needs to be thread safe, so a read-write mutex is added to protect the `eventUrnToResourceRow` map. The unused `urnToID` map was removed in passing. ## Impact There are scenarios where the total time taken for an operation was dominated by servicing the events. This reduces the time for a complex (~2000 resources) `pulumi preview` from 1m45s to 45s For a `pulumi up` with `-v=11` on a the same stack, where all the register resource spans were completing in 1h6m and the postEngineEventBatch events were taking 3h45m, this PR removes the time impact of reporting the events (greatly inflated by the high verbosity setting) and the operation takes the anticipated 1h6m <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #15668 This was happening because the renderer was being marked dirty once per second in a tick event, which caused frame to redraw. There is a check in the render method that `display.headerRow` is not nil that was previously used to prevent rendering when no events had been added. This check is now part of the `markDirty` logic Some of the tests needed to be updated to make this work and have also been refactored ## Checklist - [X] I have run `make tidy` to update any new dependencies - [X] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [X] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Paul Roberts <proberts@pulumi.com>
2024-03-18 16:53:13 +00:00
func (r *treeRenderer) handleKey(key string) {
switch key {
case terminal.KeyCtrlC:
sigint()
case terminal.KeyCtrlO:
if r.permalink != "" {
if err := browser.OpenURL(r.permalink); err != nil {
r.showStatusMessage(colors.Red+"could not open browser"+colors.Reset, 5*time.Second)
}
}
case terminal.KeyUp, "k":
if r.treeTableOffset > 0 {
r.treeTableOffset--
}
r.markDirty()
case terminal.KeyDown, "j":
if r.treeTableOffset < r.maxTreeTableOffset {
r.treeTableOffset++
}
r.markDirty()
case terminal.KeyPageUp:
_, termHeight, err := r.term.Size()
contract.IgnoreError(err)
if r.treeTableOffset > termHeight {
r.treeTableOffset -= termHeight
} else {
r.treeTableOffset = 0
}
r.markDirty()
case terminal.KeyPageDown:
_, termHeight, err := r.term.Size()
contract.IgnoreError(err)
if r.maxTreeTableOffset-r.treeTableOffset > termHeight {
r.treeTableOffset += termHeight
} else {
r.treeTableOffset = r.maxTreeTableOffset
}
r.markDirty()
case terminal.KeyHome, "g":
if r.treeTableOffset > 0 {
r.treeTableOffset = 0
}
r.markDirty()
case terminal.KeyEnd, "G":
if r.treeTableOffset < r.maxTreeTableOffset {
r.treeTableOffset = r.maxTreeTableOffset
}
r.markDirty()
}
}
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
func (r *treeRenderer) pollInput() {
for {
key, err := r.term.ReadKey()
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
if err == nil {
r.keys <- key
} else if errors.Is(err, io.EOF) {
[cli] Reimplement the interactive renderer The display pipleline looks like this: ╭──────╮ │Engine│ ╰──────╯ ⬇ engine events ╭────────────────╮ │Progress Display│ ╰────────────────╯ ⬇ display events: ticks, resource updates, system messages ╭─────────────────╮ │Progress Renderer│ ╰─────────────────╯ ⬇ text ╭────────╮ │Terminal│ ╰────────╯ The existing implementation of the interactive Progress Renderer is broken into two parts, the display renderer and the message renderer. The display renderer converts display events into progress messages, each of which generally represents a single line of text at a particular position in the output. The message renderer converts progress messages into screen updates by identifying whether or not the contents of a particular message have changed and if so, re-rendering its output line. In somewhat greater detail: ╭────────────────╮ │Display Renderer│ ╰────────────────╯ ⬇ convert resource rows into a tree table ⬇ convert the tree table and system messages into lines ⬇ convert each line into a progress message with an index ╭────────────────╮ │Message Renderer│ ╰────────────────╯ ⬇ if the line identified in a progress message has changed, ⬇ go to that line on the terminal, clear it, and update it ╭────────╮ │Terminal│ ╰────────╯ This separation of concerns is unnecessary and makes it difficult to understand where and when the terminal is updated. This approach also makes it somewhat challenging to change the way in which the display interacts with the terminal, as both the display renderer and the message renderer need to e.g. understand terminal dimensions, movement, etc. These changes reimplement the interactive Progress Renderer using a frame-oriented approach. The display is updated at 60 frame per second. If nothing has happened to invalidate the display's contents (i.e. no changes to the terminal geometry or the displayable contents have occurred), then the frame is not redrawn. Otherwise, the contents of the display are re-rendered and redrawn. An advantage of this approach is that it made it relatively simple to fix a long-standing issue with the interactive display: when the number of rows in the output exceed the height of the terminal, the new renderer clamps the output and allows the user to scroll the tree table using the up and down arrow keys.
2022-10-31 14:59:14 +00:00
close(r.keys)
return
}
}
}