Unverified Commit 6ebab38b authored by Dane Madsen's avatar Dane Madsen Committed by GitHub
Browse files

Merge branch 'jmorganca:main' into main

parents 5d8e864d a3fcecf9
2023-11-10 07:17:40 /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
2023-11-10 07:17:40 /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
2023-11-10 07:17:40 /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
2023-11-10 07:17:40 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
2023-11-10 07:17:40 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
2023-11-10 07:17:40 /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
2023-11-10 07:17:40 /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
2023-11-10 07:17:40 /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
2023-11-10 07:17:40 /docker-entrypoint.sh: Configuration complete; ready for start up
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: using the "epoll" event method
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: nginx/1.25.3
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14)
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: OS: Linux 6.4.16-linuxkit
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker processes
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker process 29
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker process 30
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker process 31
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker process 32
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker process 33
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker process 34
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker process 35
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker process 36
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker process 37
2023-11-10 07:17:40 2023/11/10 13:17:40 [notice] 1#1: start worker process 38
2023-11-10 07:17:44 192.168.65.1 - - [10/Nov/2023:13:17:43 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" "-"
2023-11-10 07:17:44 2023/11/10 13:17:44 [error] 29#29: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 192.168.65.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost:8080", referrer: "http://localhost:8080/"
2023-11-10 07:17:44 192.168.65.1 - - [10/Nov/2023:13:17:44 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://localhost:8080/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" "-"
2023-11-10 07:17:50 2023/11/10 13:17:50 [error] 29#29: *1 open() "/usr/share/nginx/html/ahstat" failed (2: No such file or directory), client: 192.168.65.1, server: localhost, request: "GET /ahstat HTTP/1.1", host: "localhost:8080"
2023-11-10 07:17:50 192.168.65.1 - - [10/Nov/2023:13:17:50 +0000] "GET /ahstat HTTP/1.1" 404 555 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" "-"
2023-11-10 07:18:53 2023/11/10 13:18:53 [error] 29#29: *1 open() "/usr/share/nginx/html/ahstat" failed (2: No such file or directory), client: 192.168.65.1, server: localhost, request: "GET /ahstat HTTP/1.1", host: "localhost:8080"
2023-11-10 07:18:53 192.168.65.1 - - [10/Nov/2023:13:18:53 +0000] "GET /ahstat HTTP/1.1" 404 555 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" "-"
# Log Analysis example
![loganalyzer 2023-11-10 08_53_29](https://github.com/jmorganca/ollama/assets/633681/ad30f1fc-321f-4953-8914-e30e24db9921)
This example shows one possible way to create a log file analyzer. To use it, run:
`python loganalysis.py <logfile>`
You can try this with the `logtest.logfile` file included in this directory.
## Review the code
The first part of this example is a Modelfile that takes `codebooga` and applies a new System Prompt:
```plaintext
SYSTEM """
You are a log file analyzer. You will receive a set of lines from a log file for some software application, find the errors and other interesting aspects of the logs, and explain them so a new user can understand what they mean. If there are any steps they can do to resolve them, list the steps in your answer.
"""
```
This model is available at https://ollama.ai/mattw/loganalyzer. You can customize it and add to your own namespace using the command `ollama create <namespace/modelname> -f <path-to-modelfile>` then `ollama push <namespace/modelname>`.
Then loganalysis.py scans all the lines in the given log file and searches for the word 'error'. When the word is found, the 10 lines before and after are set as the prompt for a call to the Generate API.
```python
data = {
"prompt": "\n".join(error_logs),
"model": "mattw/loganalyzer"
}
```
Finally, the streamed output is parsed and the response field in the output is printed to the line.
```python
response = requests.post("http://localhost:11434/api/generate", json=data, stream=True)
for line in response.iter_lines():
if line:
json_data = json.loads(line)
if json_data['done'] == False:
print(json_data['response'], end='')
```
## Next Steps
There is a lot more that can be done here. This is a simple way to detect errors, looking for the word error. Perhaps it would be interesting to find anomalous activity in the logs. It could be interesting to create embeddings for each line and compare them, looking for similar lines. Or look into applying Levenshtein Distance algorithms to find similar lines to help identify the anomalous lines.
Also try different models and different prompts to analyze the data. You could consider adding retrieval augmented generation (RAG) to this to help understand newer log formats.
package format
import "fmt"
import (
"fmt"
"math"
)
const (
Byte = 1
KiloByte = Byte * 1000
MegaByte = KiloByte * 1000
GigaByte = MegaByte * 1000
TeraByte = GigaByte * 1000
)
func HumanBytes(b int64) string {
var value float64
var unit string
switch {
case b > GigaByte:
return fmt.Sprintf("%d GB", b/GigaByte)
case b > MegaByte:
return fmt.Sprintf("%d MB", b/MegaByte)
case b > KiloByte:
return fmt.Sprintf("%d KB", b/KiloByte)
case b >= TeraByte:
value = float64(b) / TeraByte
unit = "TB"
case b >= GigaByte:
value = float64(b) / GigaByte
unit = "GB"
case b >= MegaByte:
value = float64(b) / MegaByte
unit = "MB"
case b >= KiloByte:
value = float64(b) / KiloByte
unit = "KB"
default:
return fmt.Sprintf("%d B", b)
}
switch {
case value >= 100:
return fmt.Sprintf("%d %s", int(value), unit)
case value != math.Trunc(value):
return fmt.Sprintf("%.1f %s", value, unit)
default:
return fmt.Sprintf("%d %s", int(value), unit)
}
}
......@@ -3,7 +3,6 @@ module github.com/jmorganca/ollama
go 1.20
require (
github.com/dustin/go-humanize v1.0.1
github.com/emirpasic/gods v1.18.1
github.com/gin-gonic/gin v1.9.1
github.com/mattn/go-runewidth v0.0.14
......
......@@ -7,13 +7,13 @@ package llm
//go:generate git -C ggml apply ../patches/0002-34B-model-support.patch
//go:generate git -C ggml apply ../patches/0003-metal-fix-synchronization-in-new-matrix-multiplicati.patch
//go:generate git -C ggml apply ../patches/0004-metal-add-missing-barriers-for-mul-mat-2699.patch
//go:generate cmake -S ggml -B ggml/build/cpu -DLLAMA_ACCELERATE=on -DLLAMA_K_QUANTS=on -DCMAKE_SYSTEM_PROCESSOR=x86_64 -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0
//go:generate cmake -S ggml -B ggml/build/cpu -DLLAMA_ACCELERATE=on -DLLAMA_K_QUANTS=on -DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_SYSTEM_PROCESSOR=x86_64 -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0
//go:generate cmake --build ggml/build/cpu --target server --config Release
//go:generate mv ggml/build/cpu/bin/server ggml/build/cpu/bin/ollama-runner
//go:generate git submodule update --force gguf
//go:generate git -C gguf apply ../patches/0001-update-default-log-target.patch
//go:generate git -C gguf apply ../patches/0001-metal-handle-ggml_scale-for-n-4-0-close-3754.patch
//go:generate cmake -S gguf -B gguf/build/cpu -DLLAMA_ACCELERATE=on -DLLAMA_K_QUANTS=on -DCMAKE_SYSTEM_PROCESSOR=x86_64 -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0
//go:generate cmake -S gguf -B gguf/build/cpu -DLLAMA_METAL=off -DLLAMA_ACCELERATE=on -DLLAMA_K_QUANTS=on -DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_SYSTEM_PROCESSOR=x86_64 -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DLLAMA_NATIVE=off -DLLAMA_AVX=on -DLLAMA_AVX2=on -DLLAMA_AVX512=off -DLLAMA_FMA=on -DLLAMA_F16C=on
//go:generate cmake --build gguf/build/cpu --target server --config Release
//go:generate mv gguf/build/cpu/bin/server gguf/build/cpu/bin/ollama-runner
......@@ -71,9 +71,10 @@ func chooseRunners(workDir, runnerType string) []ModelRunner {
// IMPORTANT: the order of the runners in the array is the priority order
switch runtime.GOOS {
case "darwin":
runners = []ModelRunner{
{Path: path.Join(buildPath, "metal", "bin", "ollama-runner")},
{Path: path.Join(buildPath, "cpu", "bin", "ollama-runner")},
if runtime.GOARCH == "arm64" {
runners = []ModelRunner{{Path: path.Join(buildPath, "metal", "bin", "ollama-runner")}}
} else {
runners = []ModelRunner{{Path: path.Join(buildPath, "cpu", "bin", "ollama-runner")}}
}
case "linux":
runners = []ModelRunner{
......@@ -225,7 +226,7 @@ type llama struct {
}
var (
errNvidiaSMI = errors.New("nvidia-smi command failed")
errNvidiaSMI = errors.New("warning: gpu support may not be enabled, check that you have installed GPU drivers: nvidia-smi command failed")
errAvailableVRAM = errors.New("not enough VRAM available, falling back to CPU only")
)
......@@ -342,6 +343,10 @@ func newLlama(model string, adapters []string, runners []ModelRunner, numLayers
"--embedding",
}
if opts.MainGPU > 0 {
params = append(params, "--main-gpu", fmt.Sprintf("%d", opts.MainGPU))
}
if opts.RopeFrequencyBase > 0 {
params = append(params, "--rope-freq-base", fmt.Sprintf("%f", opts.RopeFrequencyBase))
}
......@@ -543,6 +548,7 @@ func (llm *llama) Predict(ctx context.Context, prevContext []int, prompt string,
"stream": true,
"n_predict": llm.NumPredict,
"n_keep": llm.NumKeep,
"main_gpu": llm.MainGPU,
"temperature": llm.Temperature,
"top_k": llm.TopK,
"top_p": llm.TopP,
......
......@@ -41,20 +41,13 @@ func New(workDir, model string, adapters []string, opts api.Options) (LLM, error
if runtime.GOOS == "darwin" {
switch ggml.FileType() {
case "Q8_0":
case "F32", "Q5_0", "Q5_1", "Q8_0":
if ggml.Name() != "gguf" && opts.NumGPU != 0 {
// GGML Q8_0 do not support Metal API and will
// cause the runner to segmentation fault so disable GPU
log.Printf("WARNING: GPU disabled for F32, Q5_0, Q5_1, and Q8_0")
opts.NumGPU = 0
}
case "F32", "Q5_0", "Q5_1":
if opts.NumGPU != 0 {
// F32, Q5_0, Q5_1, and Q8_0 do not support Metal API and will
// cause the runner to segmentation fault so disable GPU
log.Printf("WARNING: GPU disabled for F32, Q5_0, Q5_1, and Q8_0")
opts.NumGPU = 0
}
}
var requiredMemory int64
......
package progress
import (
"fmt"
"math"
"os"
"strings"
"time"
"github.com/jmorganca/ollama/format"
"golang.org/x/term"
)
type Stats struct {
rate int64
value int64
remaining time.Duration
}
type Bar struct {
message string
messageWidth int
maxValue int64
initialValue int64
currentValue int64
started time.Time
stats Stats
statted time.Time
}
func NewBar(message string, maxValue, initialValue int64) *Bar {
return &Bar{
message: message,
messageWidth: -1,
maxValue: maxValue,
initialValue: initialValue,
currentValue: initialValue,
started: time.Now(),
}
}
func (b *Bar) String() string {
termWidth, _, err := term.GetSize(int(os.Stderr.Fd()))
if err != nil {
termWidth = 80
}
var pre, mid, suf strings.Builder
if b.message != "" {
message := strings.TrimSpace(b.message)
if b.messageWidth > 0 && len(message) > b.messageWidth {
message = message[:b.messageWidth]
}
fmt.Fprintf(&pre, "%s", message)
if b.messageWidth-pre.Len() >= 0 {
pre.WriteString(strings.Repeat(" ", b.messageWidth-pre.Len()))
}
pre.WriteString(" ")
}
fmt.Fprintf(&pre, "%3.0f%% ", math.Floor(b.percent()))
fmt.Fprintf(&suf, "(%s/%s", format.HumanBytes(b.currentValue), format.HumanBytes(b.maxValue))
stats := b.Stats()
rate := int64(stats.rate)
if rate > 0 {
fmt.Fprintf(&suf, ", %s/s", format.HumanBytes(rate))
}
fmt.Fprintf(&suf, ")")
elapsed := time.Since(b.started)
if b.percent() < 100 && rate > 0 {
fmt.Fprintf(&suf, " [%s:%s]", elapsed.Round(time.Second), stats.remaining)
} else {
fmt.Fprintf(&suf, " ")
}
mid.WriteString("▕")
// add 3 extra spaces: 2 boundary characters and 1 space at the end
f := termWidth - pre.Len() - suf.Len() - 3
n := int(float64(f) * b.percent() / 100)
if n > 0 {
mid.WriteString(strings.Repeat("█", n))
}
if f-n > 0 {
mid.WriteString(strings.Repeat(" ", f-n))
}
mid.WriteString("▏")
return pre.String() + mid.String() + suf.String()
}
func (b *Bar) Set(value int64) {
if value >= b.maxValue {
value = b.maxValue
}
b.currentValue = value
}
func (b *Bar) percent() float64 {
if b.maxValue > 0 {
return float64(b.currentValue) / float64(b.maxValue) * 100
}
return 0
}
func (b *Bar) Stats() Stats {
if time.Since(b.statted) < time.Second {
return b.stats
}
switch {
case b.statted.IsZero():
b.stats = Stats{
value: b.initialValue,
rate: 0,
remaining: 0,
}
case b.currentValue >= b.maxValue:
b.stats = Stats{
value: b.maxValue,
rate: 0,
remaining: 0,
}
default:
rate := b.currentValue - b.stats.value
var remaining time.Duration
if rate > 0 {
remaining = time.Second * time.Duration((float64(b.maxValue-b.currentValue))/(float64(rate)))
}
b.stats = Stats{
value: b.currentValue,
rate: rate,
remaining: remaining,
}
}
b.statted = time.Now()
return b.stats
}
package progress
import (
"fmt"
"io"
"sync"
"time"
)
type State interface {
String() string
}
type Progress struct {
mu sync.Mutex
w io.Writer
pos int
ticker *time.Ticker
states []State
}
func NewProgress(w io.Writer) *Progress {
p := &Progress{w: w}
go p.start()
return p
}
func (p *Progress) stop() bool {
for _, state := range p.states {
if spinner, ok := state.(*Spinner); ok {
spinner.Stop()
}
}
if p.ticker != nil {
p.ticker.Stop()
p.ticker = nil
p.render()
return true
}
return false
}
func (p *Progress) Stop() bool {
stopped := p.stop()
if stopped {
fmt.Fprint(p.w, "\n")
}
return stopped
}
func (p *Progress) StopAndClear() bool {
fmt.Fprint(p.w, "\033[?25l")
defer fmt.Fprint(p.w, "\033[?25h")
stopped := p.stop()
if stopped {
// clear all progress lines
for i := 0; i < p.pos; i++ {
if i > 0 {
fmt.Fprint(p.w, "\033[A")
}
fmt.Fprint(p.w, "\033[2K\033[1G")
}
}
return stopped
}
func (p *Progress) Add(key string, state State) {
p.mu.Lock()
defer p.mu.Unlock()
p.states = append(p.states, state)
}
func (p *Progress) render() error {
p.mu.Lock()
defer p.mu.Unlock()
fmt.Fprint(p.w, "\033[?25l")
defer fmt.Fprint(p.w, "\033[?25h")
// clear already rendered progress lines
for i := 0; i < p.pos; i++ {
if i > 0 {
fmt.Fprint(p.w, "\033[A")
}
fmt.Fprint(p.w, "\033[2K\033[1G")
}
// render progress lines
for i, state := range p.states {
fmt.Fprint(p.w, state.String())
if i < len(p.states)-1 {
fmt.Fprint(p.w, "\n")
}
}
p.pos = len(p.states)
return nil
}
func (p *Progress) start() {
p.ticker = time.NewTicker(100 * time.Millisecond)
for range p.ticker.C {
p.render()
}
}
package progress
import (
"fmt"
"strings"
"time"
)
type Spinner struct {
message string
messageWidth int
parts []string
value int
ticker *time.Ticker
started time.Time
stopped time.Time
}
func NewSpinner(message string) *Spinner {
s := &Spinner{
message: message,
parts: []string{
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏",
},
started: time.Now(),
}
go s.start()
return s
}
func (s *Spinner) String() string {
var sb strings.Builder
if len(s.message) > 0 {
message := strings.TrimSpace(s.message)
if s.messageWidth > 0 && len(message) > s.messageWidth {
message = message[:s.messageWidth]
}
fmt.Fprintf(&sb, "%s", message)
if s.messageWidth-sb.Len() >= 0 {
sb.WriteString(strings.Repeat(" ", s.messageWidth-sb.Len()))
}
sb.WriteString(" ")
}
if s.stopped.IsZero() {
spinner := s.parts[s.value]
sb.WriteString(spinner)
sb.WriteString(" ")
}
return sb.String()
}
func (s *Spinner) start() {
s.ticker = time.NewTicker(100 * time.Millisecond)
for range s.ticker.C {
s.value = (s.value + 1) % len(s.parts)
if !s.stopped.IsZero() {
return
}
}
}
func (s *Spinner) Stop() {
if s.stopped.IsZero() {
s.stopped = time.Now()
}
}
MIT License
Copyright (c) 2017 Zack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# progressbar
[![CI](https://github.com/schollz/progressbar/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/schollz/progressbar/actions/workflows/ci.yml)
[![go report card](https://goreportcard.com/badge/github.com/schollz/progressbar)](https://goreportcard.com/report/github.com/schollz/progressbar)
[![coverage](https://img.shields.io/badge/coverage-84%25-brightgreen.svg)](https://gocover.io/github.com/schollz/progressbar)
[![godocs](https://godoc.org/github.com/schollz/progressbar?status.svg)](https://godoc.org/github.com/schollz/progressbar/v3)
A very simple thread-safe progress bar which should work on every OS without problems. I needed a progressbar for [croc](https://github.com/schollz/croc) and everything I tried had problems, so I made another one. In order to be OS agnostic I do not plan to support [multi-line outputs](https://github.com/schollz/progressbar/issues/6).
## Install
```
go get -u github.com/schollz/progressbar/v3
```
## Usage
### Basic usage
```golang
bar := progressbar.Default(100)
for i := 0; i < 100; i++ {
bar.Add(1)
time.Sleep(40 * time.Millisecond)
}
```
which looks like:
![Example of basic bar](examples/basic/basic.gif)
### I/O operations
The `progressbar` implements an `io.Writer` so it can automatically detect the number of bytes written to a stream, so you can use it as a progressbar for an `io.Reader`.
```golang
req, _ := http.NewRequest("GET", "https://dl.google.com/go/go1.14.2.src.tar.gz", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
f, _ := os.OpenFile("go1.14.2.src.tar.gz", os.O_CREATE|os.O_WRONLY, 0644)
defer f.Close()
bar := progressbar.DefaultBytes(
resp.ContentLength,
"downloading",
)
io.Copy(io.MultiWriter(f, bar), resp.Body)
```
which looks like:
![Example of download bar](examples/download/download.gif)
### Progress bar with unknown length
A progressbar with unknown length is a spinner. Any bar with -1 length will automatically convert it to a spinner with a customizable spinner type. For example, the above code can be run and set the `resp.ContentLength` to `-1`.
which looks like:
![Example of download bar with unknown length](examples/download-unknown/download-unknown.gif)
### Customization
There is a lot of customization that you can do - change the writer, the color, the width, description, theme, etc. See [all the options](https://pkg.go.dev/github.com/schollz/progressbar/v3?tab=doc#Option).
```golang
bar := progressbar.NewOptions(1000,
progressbar.OptionSetWriter(ansi.NewAnsiStdout()),
progressbar.OptionEnableColorCodes(true),
progressbar.OptionShowBytes(true),
progressbar.OptionSetWidth(15),
progressbar.OptionSetDescription("[cyan][1/3][reset] Writing moshable file..."),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "[green]=[reset]",
SaucerHead: "[green]>[reset]",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}))
for i := 0; i < 1000; i++ {
bar.Add(1)
time.Sleep(5 * time.Millisecond)
}
```
which looks like:
![Example of customized bar](examples/customization/customization.gif)
## Contributing
Pull requests are welcome. Feel free to...
- Revise documentation
- Add new features
- Fix bugs
- Suggest improvements
## Thanks
Thanks [@Dynom](https://github.com/dynom) for massive improvements in version 2.0!
Thanks [@CrushedPixel](https://github.com/CrushedPixel) for adding descriptions and color code support!
Thanks [@MrMe42](https://github.com/MrMe42) for adding some minor features!
Thanks [@tehstun](https://github.com/tehstun) for some great PRs!
Thanks [@Benzammour](https://github.com/Benzammour) and [@haseth](https://github.com/haseth) for helping create v3!
Thanks [@briandowns](https://github.com/briandowns) for compiling the list of spinners.
## License
MIT
package progressbar
import (
"errors"
"fmt"
"io"
"math"
"os"
"regexp"
"strings"
"sync"
"time"
"github.com/mattn/go-runewidth"
"github.com/mitchellh/colorstring"
"golang.org/x/term"
)
// ProgressBar is a thread-safe, simple
// progress bar
type ProgressBar struct {
state state
config config
lock sync.Mutex
}
// State is the basic properties of the bar
type State struct {
CurrentPercent float64
CurrentBytes float64
SecondsSince float64
SecondsLeft float64
KBsPerSecond float64
}
type state struct {
currentNum int64
currentPercent int
lastPercent int
currentSaucerSize int
isAltSaucerHead bool
lastShown time.Time
startTime time.Time
counterTime time.Time
counterNumSinceLast int64
counterLastTenRates []float64
maxLineWidth int
currentBytes float64
finished bool
exit bool // Progress bar exit halfway
rendered string
}
type config struct {
max int64 // max number of the counter
maxHumanized string
maxHumanizedSuffix string
width int
writer io.Writer
theme Theme
renderWithBlankState bool
description string
iterationString string
ignoreLength bool // ignoreLength if max bytes not known
// whether the output is expected to contain color codes
colorCodes bool
// show rate of change in kB/sec or MB/sec
showBytes bool
// show the iterations per second
showIterationsPerSecond bool
showIterationsCount bool
// whether the progress bar should show elapsed time.
// always enabled if predictTime is true.
elapsedTime bool
showElapsedTimeOnFinish bool
// whether the progress bar should attempt to predict the finishing
// time of the progress based on the start time and the average
// number of seconds between increments.
predictTime bool
// minimum time to wait in between updates
throttleDuration time.Duration
// clear bar once finished
clearOnFinish bool
// spinnerType should be a number between 0-75
spinnerType int
// spinnerTypeOptionUsed remembers if the spinnerType was changed manually
spinnerTypeOptionUsed bool
// spinner represents the spinner as a slice of string
spinner []string
// fullWidth specifies whether to measure and set the bar to a specific width
fullWidth bool
// invisible doesn't render the bar at all, useful for debugging
invisible bool
onCompletion func()
// whether the render function should make use of ANSI codes to reduce console I/O
useANSICodes bool
// showDescriptionAtLineEnd specifies whether description should be written at line end instead of line start
showDescriptionAtLineEnd bool
}
// Theme defines the elements of the bar
type Theme struct {
Saucer string
AltSaucerHead string
SaucerHead string
SaucerPadding string
BarStart string
BarEnd string
}
// Option is the type all options need to adhere to
type Option func(p *ProgressBar)
// OptionSetWidth sets the width of the bar
func OptionSetWidth(s int) Option {
return func(p *ProgressBar) {
p.config.width = s
}
}
// OptionSpinnerType sets the type of spinner used for indeterminate bars
func OptionSpinnerType(spinnerType int) Option {
return func(p *ProgressBar) {
p.config.spinnerTypeOptionUsed = true
p.config.spinnerType = spinnerType
}
}
// OptionSpinnerCustom sets the spinner used for indeterminate bars to the passed
// slice of string
func OptionSpinnerCustom(spinner []string) Option {
return func(p *ProgressBar) {
p.config.spinner = spinner
}
}
// OptionSetTheme sets the elements the bar is constructed of
func OptionSetTheme(t Theme) Option {
return func(p *ProgressBar) {
p.config.theme = t
}
}
// OptionSetVisibility sets the visibility
func OptionSetVisibility(visibility bool) Option {
return func(p *ProgressBar) {
p.config.invisible = !visibility
}
}
// OptionFullWidth sets the bar to be full width
func OptionFullWidth() Option {
return func(p *ProgressBar) {
p.config.fullWidth = true
}
}
// OptionSetWriter sets the output writer (defaults to os.StdOut)
func OptionSetWriter(w io.Writer) Option {
return func(p *ProgressBar) {
p.config.writer = w
}
}
// OptionSetRenderBlankState sets whether or not to render a 0% bar on construction
func OptionSetRenderBlankState(r bool) Option {
return func(p *ProgressBar) {
p.config.renderWithBlankState = r
}
}
// OptionSetDescription sets the description of the bar to render in front of it
func OptionSetDescription(description string) Option {
return func(p *ProgressBar) {
p.config.description = description
}
}
// OptionEnableColorCodes enables or disables support for color codes
// using mitchellh/colorstring
func OptionEnableColorCodes(colorCodes bool) Option {
return func(p *ProgressBar) {
p.config.colorCodes = colorCodes
}
}
// OptionSetElapsedTime will enable elapsed time. Always enabled if OptionSetPredictTime is true.
func OptionSetElapsedTime(elapsedTime bool) Option {
return func(p *ProgressBar) {
p.config.elapsedTime = elapsedTime
}
}
// OptionSetPredictTime will also attempt to predict the time remaining.
func OptionSetPredictTime(predictTime bool) Option {
return func(p *ProgressBar) {
p.config.predictTime = predictTime
}
}
// OptionShowCount will also print current count out of total
func OptionShowCount() Option {
return func(p *ProgressBar) {
p.config.showIterationsCount = true
}
}
// OptionShowIts will also print the iterations/second
func OptionShowIts() Option {
return func(p *ProgressBar) {
p.config.showIterationsPerSecond = true
}
}
// OptionShowElapsedOnFinish will keep the display of elapsed time on finish
func OptionShowElapsedTimeOnFinish() Option {
return func(p *ProgressBar) {
p.config.showElapsedTimeOnFinish = true
}
}
// OptionSetItsString sets what's displayed for iterations a second. The default is "it" which would display: "it/s"
func OptionSetItsString(iterationString string) Option {
return func(p *ProgressBar) {
p.config.iterationString = iterationString
}
}
// OptionThrottle will wait the specified duration before updating again. The default
// duration is 0 seconds.
func OptionThrottle(duration time.Duration) Option {
return func(p *ProgressBar) {
p.config.throttleDuration = duration
}
}
// OptionClearOnFinish will clear the bar once its finished
func OptionClearOnFinish() Option {
return func(p *ProgressBar) {
p.config.clearOnFinish = true
}
}
// OptionOnCompletion will invoke cmpl function once its finished
func OptionOnCompletion(cmpl func()) Option {
return func(p *ProgressBar) {
p.config.onCompletion = cmpl
}
}
// OptionShowBytes will update the progress bar
// configuration settings to display/hide kBytes/Sec
func OptionShowBytes(val bool) Option {
return func(p *ProgressBar) {
p.config.showBytes = val
}
}
// OptionUseANSICodes will use more optimized terminal i/o.
//
// Only useful in environments with support for ANSI escape sequences.
func OptionUseANSICodes(val bool) Option {
return func(p *ProgressBar) {
p.config.useANSICodes = val
}
}
// OptionShowDescriptionAtLineEnd defines whether description should be written at line end instead of line start
func OptionShowDescriptionAtLineEnd() Option {
return func(p *ProgressBar) {
p.config.showDescriptionAtLineEnd = true
}
}
var defaultTheme = Theme{Saucer: "█", SaucerPadding: " ", BarStart: "▕", BarEnd: "▏"}
// NewOptions constructs a new instance of ProgressBar, with any options you specify
func NewOptions(max int, options ...Option) *ProgressBar {
return NewOptions64(int64(max), options...)
}
// NewOptions64 constructs a new instance of ProgressBar, with any options you specify
func NewOptions64(max int64, options ...Option) *ProgressBar {
b := ProgressBar{
state: getBasicState(),
config: config{
writer: os.Stdout,
theme: defaultTheme,
iterationString: "it",
width: 40,
max: max,
throttleDuration: 0 * time.Nanosecond,
elapsedTime: true,
predictTime: true,
spinnerType: 9,
invisible: false,
},
}
for _, o := range options {
o(&b)
}
if b.config.spinnerType < 0 || b.config.spinnerType > 75 {
panic("invalid spinner type, must be between 0 and 75")
}
// ignoreLength if max bytes not known
if b.config.max == -1 {
b.config.ignoreLength = true
b.config.max = int64(b.config.width)
b.config.predictTime = false
}
b.config.maxHumanized, b.config.maxHumanizedSuffix = humanizeBytes(float64(b.config.max))
if b.config.renderWithBlankState {
b.RenderBlank()
}
return &b
}
func getBasicState() state {
now := time.Now()
return state{
startTime: now,
lastShown: now,
counterTime: now,
}
}
// New returns a new ProgressBar
// with the specified maximum
func New(max int) *ProgressBar {
return NewOptions(max)
}
// DefaultBytes provides a progressbar to measure byte
// throughput with recommended defaults.
// Set maxBytes to -1 to use as a spinner.
func DefaultBytes(maxBytes int64, description ...string) *ProgressBar {
desc := ""
if len(description) > 0 {
desc = description[0]
}
return NewOptions64(
maxBytes,
OptionSetDescription(desc),
OptionSetWriter(os.Stderr),
OptionShowBytes(true),
OptionSetWidth(10),
OptionThrottle(65*time.Millisecond),
OptionShowCount(),
OptionOnCompletion(func() {
fmt.Fprint(os.Stderr, "\n")
}),
OptionSpinnerType(14),
OptionFullWidth(),
OptionSetRenderBlankState(true),
)
}
// DefaultBytesSilent is the same as DefaultBytes, but does not output anywhere.
// String() can be used to get the output instead.
func DefaultBytesSilent(maxBytes int64, description ...string) *ProgressBar {
// Mostly the same bar as DefaultBytes
desc := ""
if len(description) > 0 {
desc = description[0]
}
return NewOptions64(
maxBytes,
OptionSetDescription(desc),
OptionSetWriter(io.Discard),
OptionShowBytes(true),
OptionSetWidth(10),
OptionThrottle(65*time.Millisecond),
OptionShowCount(),
OptionSpinnerType(14),
OptionFullWidth(),
)
}
// Default provides a progressbar with recommended defaults.
// Set max to -1 to use as a spinner.
func Default(max int64, description ...string) *ProgressBar {
desc := ""
if len(description) > 0 {
desc = description[0]
}
return NewOptions64(
max,
OptionSetDescription(desc),
OptionSetWriter(os.Stderr),
OptionSetWidth(10),
OptionThrottle(65*time.Millisecond),
OptionShowCount(),
OptionShowIts(),
OptionOnCompletion(func() {
fmt.Fprint(os.Stderr, "\n")
}),
OptionSpinnerType(14),
OptionFullWidth(),
OptionSetRenderBlankState(true),
)
}
// DefaultSilent is the same as Default, but does not output anywhere.
// String() can be used to get the output instead.
func DefaultSilent(max int64, description ...string) *ProgressBar {
// Mostly the same bar as Default
desc := ""
if len(description) > 0 {
desc = description[0]
}
return NewOptions64(
max,
OptionSetDescription(desc),
OptionSetWriter(io.Discard),
OptionSetWidth(10),
OptionThrottle(65*time.Millisecond),
OptionShowCount(),
OptionShowIts(),
OptionSpinnerType(14),
OptionFullWidth(),
)
}
// String returns the current rendered version of the progress bar.
// It will never return an empty string while the progress bar is running.
func (p *ProgressBar) String() string {
return p.state.rendered
}
// RenderBlank renders the current bar state, you can use this to render a 0% state
func (p *ProgressBar) RenderBlank() error {
if p.config.invisible {
return nil
}
if p.state.currentNum == 0 {
p.state.lastShown = time.Time{}
}
return p.render()
}
// Reset will reset the clock that is used
// to calculate current time and the time left.
func (p *ProgressBar) Reset() {
p.lock.Lock()
defer p.lock.Unlock()
p.state = getBasicState()
}
// Finish will fill the bar to full
func (p *ProgressBar) Finish() error {
p.lock.Lock()
p.state.currentNum = p.config.max
p.lock.Unlock()
return p.Add(0)
}
// Exit will exit the bar to keep current state
func (p *ProgressBar) Exit() error {
p.lock.Lock()
defer p.lock.Unlock()
p.state.exit = true
if p.config.onCompletion != nil {
p.config.onCompletion()
}
return nil
}
// Add will add the specified amount to the progressbar
func (p *ProgressBar) Add(num int) error {
return p.Add64(int64(num))
}
// Set will set the bar to a current number
func (p *ProgressBar) Set(num int) error {
return p.Set64(int64(num))
}
// Set64 will set the bar to a current number
func (p *ProgressBar) Set64(num int64) error {
p.lock.Lock()
toAdd := num - int64(p.state.currentBytes)
p.lock.Unlock()
return p.Add64(toAdd)
}
// Add64 will add the specified amount to the progressbar
func (p *ProgressBar) Add64(num int64) error {
if p.config.invisible {
return nil
}
p.lock.Lock()
defer p.lock.Unlock()
if p.state.exit {
return nil
}
// error out since OptionSpinnerCustom will always override a manually set spinnerType
if p.config.spinnerTypeOptionUsed && len(p.config.spinner) > 0 {
return errors.New("OptionSpinnerType and OptionSpinnerCustom cannot be used together")
}
if p.config.max == 0 {
return errors.New("max must be greater than 0")
}
if p.state.currentNum < p.config.max {
if p.config.ignoreLength {
p.state.currentNum = (p.state.currentNum + num) % p.config.max
} else {
p.state.currentNum += num
}
}
p.state.currentBytes += float64(num)
// reset the countdown timer every second to take rolling average
p.state.counterNumSinceLast += num
if time.Since(p.state.counterTime).Seconds() > 0.5 {
p.state.counterLastTenRates = append(p.state.counterLastTenRates, float64(p.state.counterNumSinceLast)/time.Since(p.state.counterTime).Seconds())
if len(p.state.counterLastTenRates) > 10 {
p.state.counterLastTenRates = p.state.counterLastTenRates[1:]
}
p.state.counterTime = time.Now()
p.state.counterNumSinceLast = 0
}
percent := float64(p.state.currentNum) / float64(p.config.max)
p.state.currentSaucerSize = int(percent * float64(p.config.width))
p.state.currentPercent = int(percent * 100)
updateBar := p.state.currentPercent != p.state.lastPercent && p.state.currentPercent > 0
p.state.lastPercent = p.state.currentPercent
if p.state.currentNum > p.config.max {
return errors.New("current number exceeds max")
}
// always update if show bytes/second or its/second
if updateBar || p.config.showIterationsPerSecond || p.config.showIterationsCount {
return p.render()
}
return nil
}
// Clear erases the progress bar from the current line
func (p *ProgressBar) Clear() error {
return clearProgressBar(p.config, p.state)
}
// Describe will change the description shown before the progress, which
// can be changed on the fly (as for a slow running process).
func (p *ProgressBar) Describe(description string) {
p.lock.Lock()
defer p.lock.Unlock()
p.config.description = description
if p.config.invisible {
return
}
p.render()
}
// New64 returns a new ProgressBar
// with the specified maximum
func New64(max int64) *ProgressBar {
return NewOptions64(max)
}
// GetMax returns the max of a bar
func (p *ProgressBar) GetMax() int {
return int(p.config.max)
}
// GetMax64 returns the current max
func (p *ProgressBar) GetMax64() int64 {
return p.config.max
}
// ChangeMax takes in a int
// and changes the max value
// of the progress bar
func (p *ProgressBar) ChangeMax(newMax int) {
p.ChangeMax64(int64(newMax))
}
// ChangeMax64 is basically
// the same as ChangeMax,
// but takes in a int64
// to avoid casting
func (p *ProgressBar) ChangeMax64(newMax int64) {
p.config.max = newMax
if p.config.showBytes {
p.config.maxHumanized, p.config.maxHumanizedSuffix = humanizeBytes(float64(p.config.max))
}
p.Add(0) // re-render
}
// IsFinished returns true if progress bar is completed
func (p *ProgressBar) IsFinished() bool {
return p.state.finished
}
// render renders the progress bar, updating the maximum
// rendered line width. this function is not thread-safe,
// so it must be called with an acquired lock.
func (p *ProgressBar) render() error {
// make sure that the rendering is not happening too quickly
// but always show if the currentNum reaches the max
if time.Since(p.state.lastShown).Nanoseconds() < p.config.throttleDuration.Nanoseconds() &&
p.state.currentNum < p.config.max {
return nil
}
if !p.config.useANSICodes {
// first, clear the existing progress bar
err := clearProgressBar(p.config, p.state)
if err != nil {
return err
}
}
// check if the progress bar is finished
if !p.state.finished && p.state.currentNum >= p.config.max {
p.state.finished = true
if !p.config.clearOnFinish {
renderProgressBar(p.config, &p.state)
}
if p.config.onCompletion != nil {
p.config.onCompletion()
}
}
if p.state.finished {
// when using ANSI codes we don't pre-clean the current line
if p.config.useANSICodes && p.config.clearOnFinish {
err := clearProgressBar(p.config, p.state)
if err != nil {
return err
}
}
return nil
}
// then, re-render the current progress bar
w, err := renderProgressBar(p.config, &p.state)
if err != nil {
return err
}
if w > p.state.maxLineWidth {
p.state.maxLineWidth = w
}
p.state.lastShown = time.Now()
return nil
}
// State returns the current state
func (p *ProgressBar) State() State {
p.lock.Lock()
defer p.lock.Unlock()
s := State{}
s.CurrentPercent = float64(p.state.currentNum) / float64(p.config.max)
s.CurrentBytes = p.state.currentBytes
s.SecondsSince = time.Since(p.state.startTime).Seconds()
if p.state.currentNum > 0 {
s.SecondsLeft = s.SecondsSince / float64(p.state.currentNum) * (float64(p.config.max) - float64(p.state.currentNum))
}
s.KBsPerSecond = float64(p.state.currentBytes) / 1000.0 / s.SecondsSince
return s
}
// regex matching ansi escape codes
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
func getStringWidth(c config, str string, colorize bool) int {
if c.colorCodes {
// convert any color codes in the progress bar into the respective ANSI codes
str = colorstring.Color(str)
}
// the width of the string, if printed to the console
// does not include the carriage return character
cleanString := strings.Replace(str, "\r", "", -1)
if c.colorCodes {
// the ANSI codes for the colors do not take up space in the console output,
// so they do not count towards the output string width
cleanString = ansiRegex.ReplaceAllString(cleanString, "")
}
// get the amount of runes in the string instead of the
// character count of the string, as some runes span multiple characters.
// see https://stackoverflow.com/a/12668840/2733724
stringWidth := runewidth.StringWidth(cleanString)
return stringWidth
}
func renderProgressBar(c config, s *state) (int, error) {
var sb strings.Builder
averageRate := average(s.counterLastTenRates)
if len(s.counterLastTenRates) == 0 || s.finished {
// if no average samples, or if finished,
// then average rate should be the total rate
if t := time.Since(s.startTime).Seconds(); t > 0 {
averageRate = s.currentBytes / t
} else {
averageRate = 0
}
}
// show iteration count in "current/total" iterations format
if c.showIterationsCount {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(", ")
}
if !c.ignoreLength {
if c.showBytes {
currentHumanize, currentSuffix := humanizeBytes(s.currentBytes)
if currentSuffix == c.maxHumanizedSuffix {
sb.WriteString(fmt.Sprintf("%s/%s%s",
currentHumanize, c.maxHumanized, c.maxHumanizedSuffix))
} else {
sb.WriteString(fmt.Sprintf("%s%s/%s%s",
currentHumanize, currentSuffix, c.maxHumanized, c.maxHumanizedSuffix))
}
} else {
sb.WriteString(fmt.Sprintf("%.0f/%d", s.currentBytes, c.max))
}
} else {
if c.showBytes {
currentHumanize, currentSuffix := humanizeBytes(s.currentBytes)
sb.WriteString(fmt.Sprintf("%s%s", currentHumanize, currentSuffix))
} else {
sb.WriteString(fmt.Sprintf("%.0f/%s", s.currentBytes, "-"))
}
}
}
// show rolling average rate
if c.showBytes && averageRate > 0 && !math.IsInf(averageRate, 1) {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(", ")
}
currentHumanize, currentSuffix := humanizeBytes(averageRate)
sb.WriteString(fmt.Sprintf("%s%s/s", currentHumanize, currentSuffix))
}
// show iterations rate
if c.showIterationsPerSecond {
if sb.Len() == 0 {
sb.WriteString("(")
} else {
sb.WriteString(", ")
}
if averageRate > 1 {
sb.WriteString(fmt.Sprintf("%0.0f %s/s", averageRate, c.iterationString))
} else if averageRate*60 > 1 {
sb.WriteString(fmt.Sprintf("%0.0f %s/min", 60*averageRate, c.iterationString))
} else {
sb.WriteString(fmt.Sprintf("%0.0f %s/hr", 3600*averageRate, c.iterationString))
}
}
if sb.Len() > 0 {
sb.WriteString(")")
}
leftBrac, rightBrac, saucer, saucerHead := "", "", "", ""
// show time prediction in "current/total" seconds format
switch {
case c.predictTime:
rightBracNum := (time.Duration((1/averageRate)*(float64(c.max)-float64(s.currentNum))) * time.Second)
if rightBracNum.Seconds() < 0 {
rightBracNum = 0 * time.Second
}
rightBrac = rightBracNum.String()
fallthrough
case c.elapsedTime:
leftBrac = (time.Duration(time.Since(s.startTime).Seconds()) * time.Second).String()
}
if c.fullWidth && !c.ignoreLength {
width, err := termWidth()
if err != nil {
width = 80
}
amend := 1 // an extra space at eol
switch {
case leftBrac != "" && rightBrac != "":
amend = 4 // space, square brackets and colon
case leftBrac != "" && rightBrac == "":
amend = 4 // space and square brackets and another space
case leftBrac == "" && rightBrac != "":
amend = 3 // space and square brackets
}
if c.showDescriptionAtLineEnd {
amend += 1 // another space
}
c.width = width - getStringWidth(c, c.description, true) - 10 - amend - sb.Len() - len(leftBrac) - len(rightBrac)
s.currentSaucerSize = int(float64(s.currentPercent) / 100.0 * float64(c.width))
}
if s.currentSaucerSize > 0 {
if c.ignoreLength {
saucer = strings.Repeat(c.theme.SaucerPadding, s.currentSaucerSize-1)
} else {
saucer = strings.Repeat(c.theme.Saucer, s.currentSaucerSize-1)
}
// Check if an alternate saucer head is set for animation
if c.theme.AltSaucerHead != "" && s.isAltSaucerHead {
saucerHead = c.theme.AltSaucerHead
s.isAltSaucerHead = false
} else if c.theme.SaucerHead == "" || s.currentSaucerSize == c.width {
// use the saucer for the saucer head if it hasn't been set
// to preserve backwards compatibility
saucerHead = c.theme.Saucer
} else {
saucerHead = c.theme.SaucerHead
s.isAltSaucerHead = true
}
}
/*
Progress Bar format
Description % |------ | (kb/s) (iteration count) (iteration rate) (predict time)
or if showDescriptionAtLineEnd is enabled
% |------ | (kb/s) (iteration count) (iteration rate) (predict time) Description
*/
repeatAmount := c.width - s.currentSaucerSize
if repeatAmount < 0 {
repeatAmount = 0
}
str := ""
if c.ignoreLength {
selectedSpinner := spinners[c.spinnerType]
if len(c.spinner) > 0 {
selectedSpinner = c.spinner
}
spinner := selectedSpinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(selectedSpinner)))))]
if c.elapsedTime {
if c.showDescriptionAtLineEnd {
str = fmt.Sprintf("\r%s %s [%s] %s ",
spinner,
sb.String(),
leftBrac,
c.description)
} else {
str = fmt.Sprintf("\r%s %s %s [%s] ",
spinner,
c.description,
sb.String(),
leftBrac)
}
} else {
if c.showDescriptionAtLineEnd {
str = fmt.Sprintf("\r%s %s %s ",
spinner,
sb.String(),
c.description)
} else {
str = fmt.Sprintf("\r%s %s %s ",
spinner,
c.description,
sb.String())
}
}
} else if rightBrac == "" {
str = fmt.Sprintf("%4d%% %s%s%s%s%s %s",
s.currentPercent,
c.theme.BarStart,
saucer,
saucerHead,
strings.Repeat(c.theme.SaucerPadding, repeatAmount),
c.theme.BarEnd,
sb.String())
if s.currentPercent == 100 && c.showElapsedTimeOnFinish {
str = fmt.Sprintf("%s [%s]", str, leftBrac)
}
if c.showDescriptionAtLineEnd {
str = fmt.Sprintf("\r%s %s ", str, c.description)
} else {
str = fmt.Sprintf("\r%s%s ", c.description, str)
}
} else {
if s.currentPercent == 100 {
str = fmt.Sprintf("%4d%% %s%s%s%s%s %s",
s.currentPercent,
c.theme.BarStart,
saucer,
saucerHead,
strings.Repeat(c.theme.SaucerPadding, repeatAmount),
c.theme.BarEnd,
sb.String())
if c.showElapsedTimeOnFinish {
str = fmt.Sprintf("%s [%s]", str, leftBrac)
}
if c.showDescriptionAtLineEnd {
str = fmt.Sprintf("\r%s %s", str, c.description)
} else {
str = fmt.Sprintf("\r%s%s", c.description, str)
}
} else {
str = fmt.Sprintf("%4d%% %s%s%s%s%s %s [%s:%s]",
s.currentPercent,
c.theme.BarStart,
saucer,
saucerHead,
strings.Repeat(c.theme.SaucerPadding, repeatAmount),
c.theme.BarEnd,
sb.String(),
leftBrac,
rightBrac)
if c.showDescriptionAtLineEnd {
str = fmt.Sprintf("\r%s %s", str, c.description)
} else {
str = fmt.Sprintf("\r%s%s", c.description, str)
}
}
}
if c.colorCodes {
// convert any color codes in the progress bar into the respective ANSI codes
str = colorstring.Color(str)
}
s.rendered = str
return getStringWidth(c, str, false), writeString(c, str)
}
func clearProgressBar(c config, s state) error {
if s.maxLineWidth == 0 {
return nil
}
if c.useANSICodes {
// write the "clear current line" ANSI escape sequence
return writeString(c, "\033[2K\r")
}
// fill the empty content
// to overwrite the progress bar and jump
// back to the beginning of the line
str := fmt.Sprintf("\r%s\r", strings.Repeat(" ", s.maxLineWidth))
return writeString(c, str)
// the following does not show correctly if the previous line is longer than subsequent line
// return writeString(c, "\r")
}
func writeString(c config, str string) error {
if _, err := io.WriteString(c.writer, str); err != nil {
return err
}
if f, ok := c.writer.(*os.File); ok {
// ignore any errors in Sync(), as stdout
// can't be synced on some operating systems
// like Debian 9 (Stretch)
f.Sync()
}
return nil
}
// Reader is the progressbar io.Reader struct
type Reader struct {
io.Reader
bar *ProgressBar
}
// NewReader return a new Reader with a given progress bar.
func NewReader(r io.Reader, bar *ProgressBar) Reader {
return Reader{
Reader: r,
bar: bar,
}
}
// Read will read the data and add the number of bytes to the progressbar
func (r *Reader) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p)
r.bar.Add(n)
return
}
// Close the reader when it implements io.Closer
func (r *Reader) Close() (err error) {
if closer, ok := r.Reader.(io.Closer); ok {
return closer.Close()
}
r.bar.Finish()
return
}
// Write implement io.Writer
func (p *ProgressBar) Write(b []byte) (n int, err error) {
n = len(b)
p.Add(n)
return
}
// Read implement io.Reader
func (p *ProgressBar) Read(b []byte) (n int, err error) {
n = len(b)
p.Add(n)
return
}
func (p *ProgressBar) Close() (err error) {
p.Finish()
return
}
func average(xs []float64) float64 {
total := 0.0
for _, v := range xs {
total += v
}
return total / float64(len(xs))
}
func humanizeBytes(s float64) (string, string) {
sizes := []string{" B", " kB", " MB", " GB", " TB", " PB", " EB"}
base := 1000.0
if s < 10 {
return fmt.Sprintf("%2.0f", s), sizes[0]
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f"
if val < 10 {
f = "%.1f"
}
return fmt.Sprintf(f, val), suffix
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
// termWidth function returns the visible width of the current terminal
// and can be redefined for testing
var termWidth = func() (width int, err error) {
width, _, err = term.GetSize(int(os.Stdout.Fd()))
if err == nil {
return width, nil
}
return 0, err
}
package progressbar
var spinners = map[int][]string{
0: {"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"},
1: {"▁", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▁"},
2: {"▖", "▘", "▝", "▗"},
3: {"┤", "┘", "┴", "└", "├", "┌", "┬", "┐"},
4: {"◢", "◣", "◤", "◥"},
5: {"◰", "◳", "◲", "◱"},
6: {"◴", "◷", "◶", "◵"},
7: {"◐", "◓", "◑", "◒"},
8: {".", "o", "O", "@", "*"},
9: {"|", "/", "-", "\\"},
10: {"◡◡", "⊙⊙", "◠◠"},
11: {"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"},
12: {">))'>", " >))'>", " >))'>", " >))'>", " >))'>", " <'((<", " <'((<", " <'((<"},
13: {"⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"},
14: {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
15: {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"},
16: {"▉", "▊", "▋", "▌", "▍", "▎", "▏", "▎", "▍", "▌", "▋", "▊", "▉"},
17: {"■", "□", "▪", "▫"},
18: {"←", "↑", "→", "↓"},
19: {"╫", "╪"},
20: {"⇐", "⇖", "⇑", "⇗", "⇒", "⇘", "⇓", "⇙"},
21: {"⠁", "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", "⠈"},
22: {"⠈", "⠉", "⠋", "⠓", "⠒", "⠐", "⠐", "⠒", "⠖", "⠦", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈"},
23: {"⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁"},
24: {"⠋", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋"},
25: {"ヲ", "ァ", "ィ", "ゥ", "ェ", "ォ", "ャ", "ュ", "ョ", "ッ", "ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "ケ", "コ", "サ", "シ", "ス", "セ", "ソ", "タ", "チ", "ツ", "テ", "ト", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "ヒ", "フ", "ヘ", "ホ", "マ", "ミ", "ム", "メ", "モ", "ヤ", "ユ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ", "ン"},
26: {".", "..", "..."},
27: {"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▇", "▆", "▅", "▄", "▃", "▂", "▁"},
28: {".", "o", "O", "°", "O", "o", "."},
29: {"+", "x"},
30: {"v", "<", "^", ">"},
31: {">>--->", " >>--->", " >>--->", " >>--->", " >>--->", " <---<<", " <---<<", " <---<<", " <---<<", "<---<<"},
32: {"|", "||", "|||", "||||", "|||||", "|||||||", "||||||||", "|||||||", "||||||", "|||||", "||||", "|||", "||", "|"},
33: {"[ ]", "[= ]", "[== ]", "[=== ]", "[==== ]", "[===== ]", "[====== ]", "[======= ]", "[======== ]", "[========= ]", "[==========]"},
34: {"(*---------)", "(-*--------)", "(--*-------)", "(---*------)", "(----*-----)", "(-----*----)", "(------*---)", "(-------*--)", "(--------*-)", "(---------*)"},
35: {"█▒▒▒▒▒▒▒▒▒", "███▒▒▒▒▒▒▒", "█████▒▒▒▒▒", "███████▒▒▒", "██████████"},
36: {"[ ]", "[=> ]", "[===> ]", "[=====> ]", "[======> ]", "[========> ]", "[==========> ]", "[============> ]", "[==============> ]", "[================> ]", "[==================> ]", "[===================>]"},
37: {"ဝ", "၀"},
38: {"▌", "▀", "▐▄"},
39: {"🌍", "🌎", "🌏"},
40: {"◜", "◝", "◞", "◟"},
41: {"⬒", "⬔", "⬓", "⬕"},
42: {"⬖", "⬘", "⬗", "⬙"},
43: {"[>>> >]", "[]>>>> []", "[] >>>> []", "[] >>>> []", "[] >>>> []", "[] >>>>[]", "[>> >>]"},
44: {"♠", "♣", "♥", "♦"},
45: {"➞", "➟", "➠", "➡", "➠", "➟"},
46: {" | ", ` \ `, "_ ", ` \ `, " | ", " / ", " _", " / "},
47: {" . . . .", ". . . .", ". . . .", ". . . .", ". . . . ", ". . . . ."},
48: {" | ", " / ", " _ ", ` \ `, " | ", ` \ `, " _ ", " / "},
49: {"⎺", "⎻", "⎼", "⎽", "⎼", "⎻"},
50: {"▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"},
51: {"[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"},
52: {"( ● )", "( ● )", "( ● )", "( ● )", "( ●)", "( ● )", "( ● )", "( ● )", "( ● )"},
53: {"✶", "✸", "✹", "✺", "✹", "✷"},
54: {"▐|\\____________▌", "▐_|\\___________▌", "▐__|\\__________▌", "▐___|\\_________▌", "▐____|\\________▌", "▐_____|\\_______▌", "▐______|\\______▌", "▐_______|\\_____▌", "▐________|\\____▌", "▐_________|\\___▌", "▐__________|\\__▌", "▐___________|\\_▌", "▐____________|\\▌", "▐____________/|▌", "▐___________/|_▌", "▐__________/|__▌", "▐_________/|___▌", "▐________/|____▌", "▐_______/|_____▌", "▐______/|______▌", "▐_____/|_______▌", "▐____/|________▌", "▐___/|_________▌", "▐__/|__________▌", "▐_/|___________▌", "▐/|____________▌"},
55: {"▐⠂ ▌", "▐⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂▌", "▐ ⠠▌", "▐ ⡀▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐⠠ ▌"},
56: {"¿", "?"},
57: {"⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"},
58: {"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"},
59: {". ", ".. ", "...", " ..", " .", " "},
60: {".", "o", "O", "°", "O", "o", "."},
61: {"▓", "▒", "░"},
62: {"▌", "▀", "▐", "▄"},
63: {"⊶", "⊷"},
64: {"▪", "▫"},
65: {"□", "■"},
66: {"▮", "▯"},
67: {"-", "=", "≡"},
68: {"d", "q", "p", "b"},
69: {"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"},
70: {"🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "},
71: {"☗", "☖"},
72: {"⧇", "⧆"},
73: {"◉", "◎"},
74: {"㊂", "㊀", "㊁"},
75: {"⦾", "⦿"},
}
......@@ -10,6 +10,7 @@ mkdir -p dist
for TARGETARCH in arm64 amd64; do
GOOS=darwin GOARCH=$TARGETARCH go generate ./...
GOOS=darwin GOARCH=$TARGETARCH go build -o dist/ollama-darwin-$TARGETARCH
rm -rf llm/llama.cpp/*/build
done
lipo -create -output dist/ollama dist/ollama-darwin-*
......
......@@ -10,6 +10,8 @@ docker buildx build \
--platform=linux/arm64,linux/amd64 \
--build-arg=VERSION \
--build-arg=GOFLAGS \
--cache-from type=local,src=.cache \
--cache-to type=local,dest=.cache \
-f Dockerfile \
-t ollama \
.
......@@ -181,6 +181,9 @@ install_cuda_driver_apt() {
debian)
status 'Enabling contrib sources...'
$SUDO sed 's/main/contrib/' < /etc/apt/sources.list | $SUDO tee /etc/apt/sources.list.d/contrib.list > /dev/null
if [ -f "/etc/apt/sources.list.d/debian.sources" ]; then
$SUDO sed 's/main/contrib/' < /etc/apt/sources.list.d/debian.sources | $SUDO tee /etc/apt/sources.list.d/contrib.sources > /dev/null
fi
;;
esac
......
......@@ -10,6 +10,7 @@ docker buildx build \
--platform=linux/arm64,linux/amd64 \
--build-arg=VERSION \
--build-arg=GOFLAGS \
--cache-from type=local,src=.cache \
-f Dockerfile \
-t ollama/ollama -t ollama/ollama:$VERSION \
.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment