bar.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package progress
  2. import (
  3. "fmt"
  4. "math"
  5. "os"
  6. "strings"
  7. "time"
  8. "github.com/jmorganca/ollama/format"
  9. "golang.org/x/term"
  10. )
  11. type Stats struct {
  12. rate int64
  13. value int64
  14. remaining time.Duration
  15. }
  16. type Bar struct {
  17. message string
  18. messageWidth int
  19. maxValue int64
  20. initialValue int64
  21. currentValue int64
  22. started time.Time
  23. stats Stats
  24. statted time.Time
  25. }
  26. func NewBar(message string, maxValue, initialValue int64) *Bar {
  27. return &Bar{
  28. message: message,
  29. messageWidth: -1,
  30. maxValue: maxValue,
  31. initialValue: initialValue,
  32. currentValue: initialValue,
  33. started: time.Now(),
  34. }
  35. }
  36. // formatDuration limits the rendering of a time.Duration to 2 units
  37. func formatDuration(d time.Duration) string {
  38. if d >= 100*time.Hour {
  39. return "99h+"
  40. }
  41. if d >= time.Hour {
  42. return fmt.Sprintf("%dh%dm", int(d.Hours()), int(d.Minutes())%60)
  43. }
  44. return d.Round(time.Second).String()
  45. }
  46. func (b *Bar) String() string {
  47. termWidth, _, err := term.GetSize(int(os.Stderr.Fd()))
  48. if err != nil {
  49. termWidth = 80
  50. }
  51. var pre, mid, suf strings.Builder
  52. if b.message != "" {
  53. message := strings.TrimSpace(b.message)
  54. if b.messageWidth > 0 && len(message) > b.messageWidth {
  55. message = message[:b.messageWidth]
  56. }
  57. fmt.Fprintf(&pre, "%s", message)
  58. if b.messageWidth-pre.Len() >= 0 {
  59. pre.WriteString(strings.Repeat(" ", b.messageWidth-pre.Len()))
  60. }
  61. pre.WriteString(" ")
  62. }
  63. fmt.Fprintf(&pre, "%3.0f%% ", math.Floor(b.percent()))
  64. fmt.Fprintf(&suf, "(%s/%s", format.HumanBytes(b.currentValue), format.HumanBytes(b.maxValue))
  65. stats := b.Stats()
  66. rate := stats.rate
  67. if stats.value > b.initialValue && stats.value < b.maxValue {
  68. fmt.Fprintf(&suf, ", %s/s", format.HumanBytes(int64(rate)))
  69. }
  70. fmt.Fprintf(&suf, ")")
  71. var timing string
  72. if stats.value > b.initialValue && stats.value < b.maxValue {
  73. timing = fmt.Sprintf("[%s:%s]", formatDuration(time.Since(b.started)), formatDuration(stats.remaining))
  74. }
  75. // 44 is the maximum width for the stats on the right of the progress bar
  76. pad := 44 - suf.Len() - len(timing)
  77. if pad > 0 {
  78. suf.WriteString(strings.Repeat(" ", pad))
  79. }
  80. suf.WriteString(timing)
  81. // add 3 extra spaces: 2 boundary characters and 1 space at the end
  82. f := termWidth - pre.Len() - suf.Len() - 3
  83. n := int(float64(f) * b.percent() / 100)
  84. if f > 0 {
  85. mid.WriteString("▕")
  86. mid.WriteString(strings.Repeat("█", n))
  87. if f-n > 0 {
  88. mid.WriteString(strings.Repeat(" ", f-n))
  89. }
  90. mid.WriteString("▏")
  91. }
  92. return pre.String() + mid.String() + suf.String()
  93. }
  94. func (b *Bar) Set(value int64) {
  95. if value >= b.maxValue {
  96. value = b.maxValue
  97. }
  98. b.currentValue = value
  99. }
  100. func (b *Bar) percent() float64 {
  101. if b.maxValue > 0 {
  102. return float64(b.currentValue) / float64(b.maxValue) * 100
  103. }
  104. return 0
  105. }
  106. func (b *Bar) Stats() Stats {
  107. if time.Since(b.statted) < time.Second {
  108. return b.stats
  109. }
  110. switch {
  111. case b.statted.IsZero():
  112. b.stats = Stats{
  113. value: b.initialValue,
  114. rate: 0,
  115. remaining: 0,
  116. }
  117. case b.currentValue >= b.maxValue:
  118. b.stats = Stats{
  119. value: b.maxValue,
  120. rate: 0,
  121. remaining: 0,
  122. }
  123. default:
  124. rate := b.currentValue - b.stats.value
  125. var remaining time.Duration
  126. if rate > 0 {
  127. remaining = time.Second * time.Duration((float64(b.maxValue-b.currentValue))/(float64(rate)))
  128. } else {
  129. remaining = time.Duration(math.MaxInt64)
  130. }
  131. b.stats = Stats{
  132. value: b.currentValue,
  133. rate: rate,
  134. remaining: remaining,
  135. }
  136. }
  137. b.statted = time.Now()
  138. return b.stats
  139. }