progress

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 25, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package progress provides progress reporting functionality for long-running operations in analyzer-lsp providers.

The package includes:

  • ProgressReporter interface for pluggable progress reporting
  • TextReporter for console/stderr output
  • ThrottledReporter for intelligent throttling and streaming

Basic Usage

For simple console output:

reporter := progress.NewTextReporter(os.Stderr)
reporter.Report(progress.ProgressEvent{
    Stage: progress.StageProviderPrepare,
    Message: "Preparing Java provider",
    Current: 50,
    Total: 100,
})

Throttled Reporting

To avoid overwhelming consumers with too many updates, use ThrottledReporter:

baseReporter := progress.NewTextReporter(os.Stderr)
throttled := progress.NewThrottledReporter("java", baseReporter)

// Reports are automatically throttled to 500ms intervals
// First and last events are always reported regardless of timing
for i := 1; i <= total; i++ {
    throttled.Report(progress.ProgressEvent{
        Current: i,
        Total: total,
    })
}

Streaming for GRPC

ThrottledReporter supports dual-mode operation for GRPC streaming:

throttled := progress.NewThrottledReporter("java", textReporter)

// Enable streaming to a channel
eventChan := make(chan progress.ProgressEvent, 100)
throttled.EnableStreaming(eventChan)

// Events are sent to both the text reporter AND the channel
// The channel uses non-blocking sends, so slow consumers don't block progress

// When done:
throttled.DisableStreaming()
close(eventChan)

Thread Safety

All reporters are safe for concurrent use. ThrottledReporter uses fine-grained locking to minimize contention and ensure non-blocking operation.

Package progress provides real-time progress reporting for analyzer execution.

This package enables tracking and reporting of analysis progress through multiple stages including provider initialization, rule parsing, and rule execution. It supports multiple output formats (JSON, text, channel-based) and is designed to have zero overhead when disabled.

Basic usage:

// Create a text reporter
reporter := progress.NewTextReporter(os.Stderr)

// Create engine
eng := engine.CreateRuleEngine(ctx, workers, log)

// Run analysis with progress reporting
results := eng.RunRulesWithOptions(ctx, ruleSets, []engine.RunOption{
    engine.WithProgressReporter(reporter),
})

// Progress events will be automatically emitted during analysis

For programmatic consumption:

// Use channel-based reporter
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
reporter := progress.NewChannelReporter(ctx)

go func() {
    for event := range reporter.Events() {
        // Handle progress event
        fmt.Printf("Progress: %d%%\n", int(event.Percent))
    }
}()

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ChannelReporter

type ChannelReporter struct {
	// contains filtered or unexported fields
}

ChannelReporter sends progress events to a Go channel for programmatic consumption.

This reporter is designed for building custom UIs, web dashboards, or other tools that need to consume progress events in real-time within a Go program. Events are sent to a buffered channel using non-blocking sends to prevent impacting analysis performance.

Important: Always call Close() when done to release resources and signal completion to consumers.

Example:

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
reporter := progress.NewChannelReporter(ctx)

// Start consuming events in a goroutine
go func() {
    for event := range reporter.Events() {
        fmt.Printf("Progress: %d%%\n", int(event.Percent))
    }
}()

// Use reporter with engine
eng := engine.CreateRuleEngine(ctx, 10, log,
    engine.WithProgressReporter(reporter),
)

func NewChannelReporter

func NewChannelReporter(ctx context.Context, opts ...ChannelReporterOption) *ChannelReporter

NewChannelReporter creates a new channel-based progress reporter.

The reporter uses a buffered channel (capacity 100) to prevent blocking the analysis. If the consumer is slow and the buffer fills up, events will be dropped rather than blocking.

The reporter automatically closes when the provided context is cancelled, following the standard Go pattern for shutdown logic. You can also manually call Close() when finished to release resources.

Optional: Pass WithLogger to log when events are dropped due to backpressure.

Example:

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
reporter := progress.NewChannelReporter(ctx, progress.WithLogger(log))
// When ctx is cancelled, the channel will automatically close

func (*ChannelReporter) Close

func (c *ChannelReporter) Close()

Close closes the events channel, signaling to consumers that no more events will be sent.

Note: The reporter automatically closes when the context passed to NewChannelReporter is cancelled, so explicit Close() calls are often unnecessary. However, it's safe to call Close() multiple times, and subsequent calls have no effect.

You can still manually close if needed:

reporter.Close()

func (*ChannelReporter) DroppedEvents

func (c *ChannelReporter) DroppedEvents() uint64

DroppedEvents returns the number of events that were dropped due to the channel buffer being full.

This can be used to monitor backpressure. If this number is high, consider:

  • Increasing the buffer size in NewChannelReporter
  • Optimizing the event consumer to process events faster
  • Reducing the frequency of progress reporting

func (*ChannelReporter) Events

func (c *ChannelReporter) Events() <-chan ProgressEvent

Events returns the read-only channel for receiving progress events.

Consumers should range over this channel to process events:

for event := range reporter.Events() {
    // Process event
}

The channel will be closed when Close() is called.

func (*ChannelReporter) Report

func (c *ChannelReporter) Report(event ProgressEvent)

Report sends a progress event to the channel.

This method uses a non-blocking send. If the channel buffer is full (meaning the consumer is not keeping up), the event will be dropped to avoid blocking the analysis. This ensures progress reporting never impacts performance.

If the reporter has been closed, this method returns immediately without panicking, ensuring safe concurrent usage.

If the event's Timestamp is zero, it will be set to the current time.

type ChannelReporterOption

type ChannelReporterOption func(*ChannelReporter)

ChannelReporterOption is a function that configures a ChannelReporter.

func WithLogger

func WithLogger(log logr.Logger) ChannelReporterOption

WithLogger sets a logger for the ChannelReporter to log dropped events.

type JSONReporter

type JSONReporter struct {
	// contains filtered or unexported fields
}

JSONReporter writes progress events as newline-delimited JSON (NDJSON).

Each event is marshaled to JSON and written as a single line, making the output suitable for streaming consumption by other tools. This reporter is thread-safe and suitable for concurrent use.

Example output:

{"timestamp":"2024-10-29T17:06:14Z","stage":"provider_init","message":"Initializing nodejs provider"}
{"timestamp":"2024-10-29T17:06:17Z","stage":"provider_init","message":"Provider nodejs ready"}
{"timestamp":"2024-10-29T17:06:22Z","stage":"rule_execution","current":1,"total":10,"percent":10}

func NewJSONReporter

func NewJSONReporter(w io.Writer) *JSONReporter

NewJSONReporter creates a new JSON progress reporter that writes to w.

The writer can be os.Stdout, os.Stderr, a file, or any io.Writer. Each progress event will be written as a single JSON line.

Example:

reporter := progress.NewJSONReporter(os.Stderr)
reporter.Report(progress.ProgressEvent{
    Stage: progress.StageRuleExecution,
    Current: 5,
    Total: 10,
    Percent: 50.0,
})

func (*JSONReporter) Report

func (j *JSONReporter) Report(event ProgressEvent)

Report writes a progress event as a JSON line.

If the event's Timestamp is zero, it will be set to the current time. Errors during JSON marshaling or writing are silently ignored to avoid disrupting the analysis.

This method is safe for concurrent use.

type NoopReporter

type NoopReporter struct{}

NoopReporter is a no-op implementation of ProgressReporter that discards all events.

This is the default reporter when progress reporting is not explicitly configured, ensuring zero overhead when the feature is disabled. All Report calls are no-ops and will be optimized away by the compiler in many cases.

Example:

// Default behavior - no progress output
reporter := progress.NewNoopReporter()
reporter.Report(event) // Does nothing

func NewNoopReporter

func NewNoopReporter() *NoopReporter

NewNoopReporter creates a new no-op progress reporter.

This is used as the default reporter when --progress-output is not specified, ensuring that progress reporting has zero overhead when disabled.

func (*NoopReporter) Report

func (n *NoopReporter) Report(event ProgressEvent)

Report discards the event without any action.

This method is intentionally empty and will be optimized away by the compiler in most cases, ensuring zero runtime overhead.

type ProgressBarReporter

type ProgressBarReporter struct {
	// contains filtered or unexported fields
}

ProgressBarReporter writes progress as a visual progress bar with real-time updates.

This reporter displays a dynamic progress bar that updates in place using carriage returns. It shows percentage completion, a visual bar, current/total counts, and the current rule being processed.

Example output:

Processing rules  42% |██████████░░░░░░░░░░░░░░░| 99/235  hibernate6-00280

The bar updates in place during rule execution and prints a final newline when complete. This reporter is designed for terminal (TTY) output where ANSI control characters work. For non-TTY output (pipes, files), consider using TextReporter instead.

func NewProgressBarReporter

func NewProgressBarReporter(w io.Writer) *ProgressBarReporter

NewProgressBarReporter creates a new progress bar reporter that writes to w.

The writer is typically os.Stderr for terminal output. The progress bar will dynamically update in place using carriage returns (\r).

Example:

reporter := progress.NewProgressBarReporter(os.Stderr)
// Progress bar will be displayed during rule execution

func (*ProgressBarReporter) Report

func (p *ProgressBarReporter) Report(event ProgressEvent)

Report processes a progress event and updates the progress bar.

The output format varies by stage:

  • Provider init: "<message>\n"
  • Rule parsing: "Loaded X rules\n"
  • Rule execution: "Processing rules XX% |█████░░░| X/Y rule-name" (updates in-place)
  • Dependency analysis: "Analyzing dependencies...\n"
  • Complete: "Analysis complete!\n"

During rule execution, the progress bar updates in-place using carriage returns (\r) until reaching 100%, at which point a newline is printed.

If the event's Timestamp is zero, it will be set to the current time. This method is safe for concurrent use.

type ProgressEvent

type ProgressEvent struct {
	// Timestamp is when the event occurred. If not set by the caller,
	// reporters will populate it automatically.
	Timestamp time.Time `json:"timestamp"`

	// Stage indicates which phase of analysis this event relates to.
	Stage Stage `json:"stage"`

	// Message provides human-readable context (e.g., rule ID, provider name).
	Message string `json:"message,omitempty"`

	// Current is the number of items completed so far (e.g., rules processed).
	Current int `json:"current,omitempty"`

	// Total is the total number of items to process.
	Total int `json:"total,omitempty"`

	// Percent is the completion percentage (0-100).
	// This field is automatically calculated from Current and Total if not set.
	Percent float64 `json:"percent,omitempty"`

	// Metadata contains additional stage-specific information.
	// For example, error details for failed providers.
	Metadata map[string]interface{} `json:"metadata,omitempty"`
}

ProgressEvent represents a progress update at a specific point in time.

Events are emitted at key points during analysis:

  • Provider initialization (start and completion)
  • Rule parsing (total count discovered)
  • Rule execution (per-rule completion with percentage)
  • Analysis completion

Not all fields are populated for all events. For example, init events may only have Stage and Message, while rule execution events include Current, Total, and Percent.

type ProgressReporter

type ProgressReporter interface {
	// Report emits a progress event. This method may be called concurrently
	// and should not block. Implementations should handle errors internally
	// to avoid disrupting the analysis.
	Report(event ProgressEvent)
}

ProgressReporter is the interface for reporting analysis progress.

Implementations must be safe for concurrent use. The Report method should not block to avoid impacting analysis performance.

type Stage

type Stage string

Stage represents a phase of the analysis process.

Stages occur in a typical sequence:

  1. StageInit - Analysis starting
  2. StageProviderInit - Initializing language providers
  3. StageRuleParsing - Loading and parsing rules
  4. StageRuleExecution - Processing rules
  5. StageComplete - Analysis finished
const (
	// StageInit indicates analysis initialization.
	StageInit Stage = "init"

	// StageProviderInit indicates provider initialization.
	// Events include provider name and readiness status.
	StageProviderInit Stage = "provider_init"

	// StageProviderPrepare indicates provider Prepare() phase (symbol cache population).
	// Events include provider name, files processed, and total files.
	StageProviderPrepare Stage = "provider_prepare"

	// StageDependencyResolution indicates dependencies resolution (list).
	// Events include the total number of dependencies discovered.
	StageDependencyResolution Stage = "dependency_resolution"

	// StageRuleParsing indicates rule loading and parsing.
	// Events include the total number of rules discovered.
	StageRuleParsing Stage = "rule_parsing"

	// StageRuleExecution indicates rule processing.
	// Events include current/total counts and percentage completion.
	StageRuleExecution Stage = "rule_execution"

	// StageDependencyAnalysis indicates dependency analysis (future).
	StageDependencyAnalysis Stage = "dependency_analysis"

	// StageComplete indicates analysis completion.
	StageComplete Stage = "complete"
)

type TextReporter

type TextReporter struct {
	// contains filtered or unexported fields
}

TextReporter writes progress events as human-readable text with timestamps.

Each event is formatted with a timestamp prefix and stage-appropriate message. This reporter is ideal for terminal output and log files where human readability is important. It is thread-safe and suitable for concurrent use.

Example output:

[17:06:14] Provider: Initializing nodejs provider
[17:06:17] Provider: Provider nodejs ready
[17:06:22] Rule: Starting rule execution: 10 rules to process
[17:06:22] Rule: patternfly-v5-to-v6-charts-00000
[17:06:26] Analysis complete!

func NewTextReporter

func NewTextReporter(w io.Writer) *TextReporter

NewTextReporter creates a new text progress reporter that writes to w.

The writer is typically os.Stderr for terminal output, but can be any io.Writer including files or custom writers.

Example:

reporter := progress.NewTextReporter(os.Stderr)
reporter.Report(progress.ProgressEvent{
    Stage: progress.StageRuleExecution,
    Message: "Processing rule-001",
})

func (*TextReporter) Report

func (t *TextReporter) Report(event ProgressEvent)

Report writes a progress event as human-readable text.

The output format varies by stage:

  • Provider init: "[HH:MM:SS] Provider: <message>"
  • Provider prepare: "[HH:MM:SS] <message>... X/Y files (Z%)"
  • Rule execution: "[HH:MM:SS] Rule: <rule-id>" or "[HH:MM:SS] Processing rules: X/Y (Z%)"
  • Complete: "[HH:MM:SS] Analysis complete!"

If the event's Timestamp is zero, it will be set to the current time. This method is safe for concurrent use.

type ThrottledReporter

type ThrottledReporter struct {
	// contains filtered or unexported fields
}

ThrottledReporter wraps a ProgressReporter with intelligent throttling and optional streaming.

It ensures progress updates don't overwhelm the reporting system by:

  • Throttling updates to a configured interval (default 500ms)
  • Always reporting first and last events regardless of timing
  • Optionally streaming events to a channel for real-time consumers

This reporter is designed to be reused across different providers and contexts. It's safe for concurrent use.

Example usage:

baseReporter := progress.NewTextReporter(os.Stderr)
throttled := progress.NewThrottledReporter("java", baseReporter)

// Optional: enable streaming for gRPC or other consumers
eventChan := make(chan ProgressEvent, 100)
throttled.EnableStreaming(eventChan)

// Report progress - automatically throttled
for i := 0; i < total; i++ {
    throttled.Report(ProgressEvent{
        Stage: StageProviderPrepare,
        Current: i,
        Total: total,
    })
}

func NewThrottledReporter

func NewThrottledReporter(stageName string, reporter ProgressReporter) *ThrottledReporter

NewThrottledReporter creates a new throttled reporter with default 500ms interval.

Parameters:

  • stageName: Default stage name for events (e.g., "provider_prepare")
  • reporter: Underlying reporter to receive throttled events (can be nil for stream-only mode)

The reporter will automatically:

  • Report the first event (current == 1)
  • Report the last event (current == total)
  • Throttle intermediate events to once per 500ms

func NewThrottledReporterWithInterval

func NewThrottledReporterWithInterval(stageName string, reporter ProgressReporter, interval time.Duration) *ThrottledReporter

NewThrottledReporterWithInterval creates a throttled reporter with custom throttle interval.

func (*ThrottledReporter) DisableStreaming

func (t *ThrottledReporter) DisableStreaming()

DisableStreaming disables event streaming. The stream channel will no longer receive events.

func (*ThrottledReporter) EnableStreaming

func (t *ThrottledReporter) EnableStreaming(ch chan<- ProgressEvent)

EnableStreaming enables event streaming to the provided channel.

Events will be sent using non-blocking sends, so a full or closed channel will not block progress reporting. Configure the channel with adequate buffering for your use case.

Channel Ownership: The caller retains ownership of the channel and is responsible for closing it. Always call DisableStreaming() before closing the channel to avoid races. The ThrottledReporter never closes the channel - it only sends to it.

Example:

eventChan := make(chan ProgressEvent, 100)
reporter.EnableStreaming(eventChan)

go func() {
    for event := range eventChan {
        // Process event
    }
}()

// When done:
reporter.DisableStreaming()  // Must be called before close!
close(eventChan)

func (*ThrottledReporter) Report

func (t *ThrottledReporter) Report(event ProgressEvent)

Report sends a progress event through the throttled reporter.

The event will be:

  • Sent immediately if it's the first event (Current == 1)
  • Sent immediately if it's the last event (Current == Total)
  • Sent immediately if throttleInterval has elapsed since last report
  • Dropped otherwise (to avoid overwhelming the reporter)

If streaming is enabled, the event will also be sent to the stream channel using a non-blocking send (dropped if channel is full).

The event will be normalized (timestamp set, percent calculated) before delivery.

func (*ThrottledReporter) Reset

func (t *ThrottledReporter) Reset()

Reset resets the throttling state. This is useful when reusing the reporter for a new operation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL