pkg/inspection/metadata/progress/progress.go (136 lines of code) (raw):
// Copyright 2024 Google LLC
//
// 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 progress
import (
"fmt"
"sync"
"github.com/GoogleCloudPlatform/khi/pkg/common/typedmap"
"github.com/GoogleCloudPlatform/khi/pkg/inspection/metadata"
"github.com/GoogleCloudPlatform/khi/pkg/task"
)
var ProgressMetadataKey = metadata.NewMetadataKey[*Progress]("progress")
const TASK_PHASE_RUNNING = "RUNNING"
const TASK_PHASE_DONE = "DONE"
const TASK_PHASE_ERROR = "ERROR"
const TASK_PHASE_CANCELLED = "CANCELLED"
type TaskProgress struct {
Id string `json:"id"`
Label string `json:"label"`
Message string `json:"message"`
Percentage float32 `json:"percentage"`
Indeterminate bool `json:"indeterminate"`
}
func NewTaskProgress(id string) *TaskProgress {
return &TaskProgress{
Id: id,
Indeterminate: false,
Percentage: 0,
Message: "",
Label: id,
}
}
// Update updates fields from percentage and message
func (tp *TaskProgress) Update(percentage float32, message string) {
tp.Percentage = percentage
tp.Message = message
tp.Indeterminate = false
}
// MarkIndeterminate updates TaskProgress field to be indeterminate mode
func (tp *TaskProgress) MarkIndeterminate() {
tp.Indeterminate = true
tp.Percentage = 0
}
type Progress struct {
Phase string `json:"phase"`
TotalProgress *TaskProgress `json:"totalProgress"`
TaskProgresses []*TaskProgress `json:"progresses"`
totalTaskCount int `json:"-"`
resolvedTaskCount int `json:"-"`
lock sync.Mutex `json:"-"`
}
func NewProgress() *Progress {
return &Progress{
Phase: TASK_PHASE_RUNNING,
TaskProgresses: make([]*TaskProgress, 0),
TotalProgress: NewTaskProgress("Total"),
lock: sync.Mutex{},
resolvedTaskCount: 0,
totalTaskCount: 0,
}
}
// Labels implements Metadata.
func (*Progress) Labels() *typedmap.ReadonlyTypedMap {
return task.NewLabelSet(
metadata.IncludeInTaskList(),
)
}
// ToSerializable implements Metadata.
func (p *Progress) ToSerializable() interface{} {
return p
}
func (p *Progress) SetTotalTaskCount(count int) {
p.totalTaskCount = count
p.updateTotalTaskProgress()
}
func (p *Progress) GetTaskProgress(id string) (*TaskProgress, error) {
p.lock.Lock()
defer p.lock.Unlock()
if p.Phase != TASK_PHASE_RUNNING {
return nil, fmt.Errorf("the current progress phase is not RUNNING but %s", p.Phase)
}
for _, progress := range p.TaskProgresses {
if progress.Id == id {
return progress, nil
}
}
taskProgress := NewTaskProgress(id)
p.TaskProgresses = append(p.TaskProgresses, taskProgress)
return taskProgress, nil
}
func (p *Progress) ResolveTask(id string) error {
p.lock.Lock()
defer p.lock.Unlock()
if p.Phase != TASK_PHASE_RUNNING {
return fmt.Errorf("the current progress phase is not RUNNING but %s", p.Phase)
}
newTaskProgress := make([]*TaskProgress, 0)
for _, progress := range p.TaskProgresses {
if progress.Id != id {
newTaskProgress = append(newTaskProgress, progress)
}
}
p.TaskProgresses = newTaskProgress
p.resolvedTaskCount += 1
p.updateTotalTaskProgress()
return nil
}
func (p *Progress) Done() error {
p.lock.Lock()
defer p.lock.Unlock()
if p.Phase != TASK_PHASE_RUNNING {
return fmt.Errorf("the current progress phase is not RUNNING but %s", p.Phase)
}
p.Phase = TASK_PHASE_DONE
p.resolvedTaskCount = p.totalTaskCount
p.TaskProgresses = make([]*TaskProgress, 0)
p.updateTotalTaskProgress()
return nil
}
func (p *Progress) Cancel() error {
p.lock.Lock()
defer p.lock.Unlock()
if p.Phase != TASK_PHASE_RUNNING {
return fmt.Errorf("the current progress phase is not RUNNING but %s", p.Phase)
}
p.Phase = TASK_PHASE_CANCELLED
p.TaskProgresses = make([]*TaskProgress, 0)
return nil
}
func (p *Progress) Error() error {
p.lock.Lock()
defer p.lock.Unlock()
if p.Phase != TASK_PHASE_RUNNING {
return fmt.Errorf("the current progress phase is not RUNNING but %s", p.Phase)
}
p.Phase = TASK_PHASE_ERROR
p.TaskProgresses = make([]*TaskProgress, 0)
return nil
}
func (p *Progress) updateTotalTaskProgress() {
p.TotalProgress.Message = fmt.Sprintf("%d of %d tasks complete", p.resolvedTaskCount, p.totalTaskCount)
p.TotalProgress.Percentage = float32(p.resolvedTaskCount) / float32(p.totalTaskCount)
}