pulumi/pkg/backend/display/jsonmessage.go

384 lines
11 KiB
Go
Raw Normal View History

2018-05-22 19:43:36 +00:00
// Copyright 2016-2018, 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
// forked from: https://github.com/moby/moby/blob/master/pkg/jsonmessage/jsonmessage.go
// so we can customize parts of the display of our progress messages
import (
"fmt"
"io"
"os"
Enable perfsprint linter (#14813) <!--- 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 <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## 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 - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] 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. -->
2023-12-12 12:19:42 +00:00
"strconv"
"unicode/utf8"
"github.com/pulumi/pulumi/pkg/v3/backend/display/internal/terminal"
"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/cmdutil"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)
// Progress describes a message we want to show in the display. There are two types of messages,
// simple 'Messages' which just get printed out as a single uninterpreted line, and 'Actions' which
// are placed and updated in the progress-grid based on their ID. Messages do not need an ID, while
// Actions must have an ID.
type Progress struct {
ID string
Message string
Action string
}
func makeMessageProgress(message string) Progress {
return Progress{Message: message}
}
func makeActionProgress(id string, action string) Progress {
contract.Assertf(id != "", "id must be non empty for action %s", action)
contract.Assertf(action != "", "action must be non empty")
return Progress{ID: id, Action: action}
}
// Display displays the Progress to `out`. `termInfo` is non-nil if `out` is a terminal.
func (jm *Progress) Display(out io.Writer, termInfo terminal.Info) {
var endl string
if termInfo != nil && /*jm.Stream == "" &&*/ jm.Action != "" {
termInfo.ClearLine(out)
endl = "\r"
fmt.Fprint(out, endl)
}
if jm.Action != "" && termInfo != nil {
fmt.Fprintf(out, "%s%s", jm.Action, endl)
} else {
var msg string
if jm.Action != "" {
msg = jm.Action
} else {
msg = jm.Message
}
fmt.Fprintf(out, "%s%s\n", msg, endl)
}
}
type messageRenderer struct {
opts Options
isInteractive bool
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
terminal terminal.Terminal
terminalWidth int
terminalHeight int
// A spinner to use to show that we're still doing work even when no output has been
// printed to the console in a while.
nonInteractiveSpinner cmdutil.Spinner
progressOutput chan<- Progress
[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
closed <-chan bool
// Cache of lines we've already printed. We don't print a progress message again if it hasn't
// changed between the last time we printed and now.
printedProgressCache map[string]Progress
}
func newInteractiveMessageRenderer(term terminal.Terminal, opts Options) progressRenderer {
r := newMessageRenderer(term, opts, true)
r.terminal = term
var err error
r.terminalWidth, r.terminalHeight, err = term.Size()
contract.IgnoreError(err)
return r
}
func newNonInteractiveRenderer(stdout io.Writer, op string, opts Options) progressRenderer {
[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
spinner, ticker := cmdutil.NewSpinnerAndTicker(
fmt.Sprintf("%s%s...", cmdutil.EmojiOr("✨ ", "@ "), op),
nil, opts.Color, 1 /*timesPerSecond*/, opts.SuppressProgress)
[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.Stop()
r := newMessageRenderer(stdout, opts, false)
r.nonInteractiveSpinner = spinner
return r
}
func newMessageRenderer(out io.Writer, opts Options, isInteractive bool) *messageRenderer {
progressOutput, closed := make(chan Progress), make(chan bool)
go func() {
ShowProgressOutput(progressOutput, out, isInteractive)
close(closed)
}()
[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 &messageRenderer{
opts: opts,
isInteractive: isInteractive,
progressOutput: progressOutput,
closed: closed,
printedProgressCache: make(map[string]Progress),
[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 *messageRenderer) Close() error {
close(r.progressOutput)
[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.closed
return nil
}
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 *messageRenderer) initializeDisplay(display *ProgressDisplay) {
r.display = display
}
// Converts the colorization tags in a progress message and then actually writes the progress
// message to the output stream. This should be the only place in this file where we actually
// process colorization tags.
func (r *messageRenderer) colorizeAndWriteProgress(progress Progress) {
if progress.Message != "" {
progress.Message = r.opts.Color.Colorize(progress.Message)
}
if progress.Action != "" {
progress.Action = r.opts.Color.Colorize(progress.Action)
}
if progress.ID != "" {
// don't repeat the same output if there is no difference between the last time we
// printed it and now.
lastProgress, has := r.printedProgressCache[progress.ID]
if has && lastProgress.Message == progress.Message && lastProgress.Action == progress.Action {
return
}
r.printedProgressCache[progress.ID] = progress
}
if !r.isInteractive {
// We're about to display something. Reset our spinner so that it will go on the next line.
r.nonInteractiveSpinner.Reset()
}
r.progressOutput <- progress
}
func (r *messageRenderer) writeSimpleMessage(msg string) {
r.colorizeAndWriteProgress(makeMessageProgress(msg))
}
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 *messageRenderer) println(line string) {
r.writeSimpleMessage(line)
}
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 *messageRenderer) tick() {
if r.isInteractive {
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.render(false)
} else {
// Update the spinner to let the user know that that work is still happening.
r.nonInteractiveSpinner.Tick()
}
}
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 *messageRenderer) renderRow(id string, colorizedColumns []string, maxColumnLengths []int,
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
) {
row := renderRow(colorizedColumns, maxColumnLengths)
if r.isInteractive {
// 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 := r.terminalWidth - 1
if maxRowLength < 0 {
maxRowLength = 0
}
row = colors.TrimColorizedString(row, maxRowLength)
}
if row != "" {
if r.isInteractive {
r.colorizeAndWriteProgress(makeActionProgress(id, row))
} else {
r.writeSimpleMessage(row)
}
}
}
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 *messageRenderer) rowUpdated(row Row) {
if r.isInteractive {
// if we're in a terminal, then refresh everything so that all our columns line up
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.render(false)
} else {
// otherwise, just print out this single row.
colorizedColumns := row.ColorizedColumns()
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
colorizedColumns[r.display.suffixColumn] += row.ColorizedSuffix()
r.renderRow("", colorizedColumns, nil)
}
}
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 *messageRenderer) systemMessage(payload engine.StdoutEventPayload) {
if r.isInteractive {
// if we're in a terminal, then refresh everything. The system events will come after
// all the normal 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
r.render(false)
} else {
// otherwise, in a non-terminal, just print out the actual event.
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.writeSimpleMessage(renderStdoutColorEvent(payload, r.display.opts))
}
}
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 *messageRenderer) done() {
if r.isInteractive {
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.render(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
}
}
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 *messageRenderer) render(done bool) {
if !r.isInteractive || r.display.headerRow == nil {
return
}
// make sure our stored dimension info is up to date
r.updateTerminalDimensions()
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)
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*/, "")
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 {
runeCount := utf8.RuneCountInString(v)
if runeCount > maxSuffixLength {
maxSuffixLength = runeCount
}
}
var rows [][]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, &rows, &maxColumnLengths)
removeInfoColumnIfUnneeded(rows)
for i, row := range 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
r.renderRow(strconv.Itoa(i), row, maxColumnLengths)
}
systemID := len(rows)
printedHeader := 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
for _, payload := range r.display.systemEventPayloads {
msg := payload.Color.Colorize(payload.Message)
lines := splitIntoDisplayableLines(msg)
if len(lines) == 0 {
continue
}
if !printedHeader {
printedHeader = true
r.colorizeAndWriteProgress(makeActionProgress(
Enable perfsprint linter (#14813) <!--- 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 <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## 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 - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] 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. -->
2023-12-12 12:19:42 +00:00
strconv.Itoa(systemID), " "))
systemID++
r.colorizeAndWriteProgress(makeActionProgress(
Enable perfsprint linter (#14813) <!--- 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 <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## 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 - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] 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. -->
2023-12-12 12:19:42 +00:00
strconv.Itoa(systemID),
colors.Yellow+"System Messages"+colors.Reset))
systemID++
}
for _, line := range lines {
r.colorizeAndWriteProgress(makeActionProgress(
Enable perfsprint linter (#14813) <!--- 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 <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## 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 - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] 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. -->
2023-12-12 12:19:42 +00:00
strconv.Itoa(systemID), " "+line))
systemID++
}
}
[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 {
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.println("")
[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 our stored dimension info is up to date.
func (r *messageRenderer) updateTerminalDimensions() {
currentTerminalWidth, currentTerminalHeight, err := r.terminal.Size()
contract.IgnoreError(err)
if currentTerminalWidth != r.terminalWidth ||
currentTerminalHeight != r.terminalHeight {
r.terminalWidth = currentTerminalWidth
r.terminalHeight = currentTerminalHeight
// also clear our display cache as we want to reprint all lines.
r.printedProgressCache = make(map[string]Progress)
}
}
// ShowProgressOutput displays a progress stream from `in` to `out`, `isInteractive` describes if
// `out` is a terminal. If this is the case, it will print `\n` at the end of each line and move the
// cursor while displaying.
func ShowProgressOutput(in <-chan Progress, out io.Writer, isInteractive bool) {
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
ids := make(map[string]int)
var info terminal.Info
if isInteractive {
term := os.Getenv("TERM")
if term == "" {
term = "vt102"
}
info = terminal.OpenInfo(term)
}
for jm := range in {
diff := 0
if jm.Action != "" {
if jm.ID == "" {
contract.Failf("Must have an ID if we have an action! %s", jm.Action)
}
line, ok := ids[jm.ID]
if !ok {
// NOTE: This approach of using len(id) to
// figure out the number of lines of history
// only works as long as we clear the history
// when we output something that's not
// accounted for in the map, such as a line
// with no ID.
line = len(ids)
ids[jm.ID] = line
if info != nil {
fmt.Fprintf(out, "\n")
}
}
diff = len(ids) - line
if info != nil {
info.CursorUp(out, diff)
}
} else {
// When outputting something that isn't progress
// output, clear the history of previous lines. We
// don't want progress entries from some previous
// operation to be updated (for example, pull -a
// with multiple tags).
ids = make(map[string]int)
}
jm.Display(out, info)
if jm.Action != "" && info != nil {
info.CursorDown(out, diff)
}
}
}