├── .github └── workflows │ └── go.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── go.mod ├── handles.go ├── log.go ├── stats.go ├── vago.go └── vago_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | 8 | strategy: 9 | matrix: 10 | go: ['1.19', '1.20'] 11 | varnish: ['6.0', '7.2', '7.3'] 12 | 13 | name: Go ${{ matrix.go }} - Varnish ${{ matrix.varnish }} 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/setup-go@v4 18 | with: 19 | go-version: ${{ matrix.go }} 20 | - uses: varnishcache-friends/setup-varnish@v1 21 | with: 22 | version: ${{ matrix.varnish }} 23 | - uses: actions/checkout@v3 24 | - run: | 25 | sudo install -d -o $USER /var/lib/varnish 26 | /usr/local/sbin/varnishd -a :1234 -b :1235 27 | go test -v 28 | go vet -unsafeptr=false 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # vago 1.4.0 (17-Sep-2022) 2 | 3 | ## Changes 4 | 5 | - Update varnish versions to 6.0, 7.1 and 7.2 6 | - Update tests to use go 1.18 and 1.18 7 | - Repository moved to varnishcache-friends 8 | 9 | # vago v1.3.0 (01-Feb-2019) 10 | 11 | ## Changes 12 | 13 | - Test against Varnish 6.0 and 6.1 14 | - Test against go 1.9, 1.10 and 1.11 15 | 16 | ## Fixes 17 | 18 | - #14 Re-attach to the cursor when varnishd restarts 19 | - dispatchCallback should return a C.int 20 | 21 | # vago v1.2.0 (17-Feb-2018) 22 | 23 | ## Changes 24 | 25 | - #12 Support Varnish 5.2 26 | 27 | # vago v1.0.1 (26-Mar-2017) 28 | 29 | ## Fixes 30 | 31 | - #9 Handle abandoned and overrun cases 32 | - #8 Make Close() goroutine safe 33 | 34 | ## Changes 35 | 36 | - #10 Add support to handle cursor options 37 | - #7 Add support for configuration parameters 38 | - Improve error handling 39 | - Test against go1.8 40 | 41 | # vago v1.0.0 (01-Feb-2017) 42 | 43 | - Initial release 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Jimena Cabrera Notari 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vago 2 | 3 | ![ci](https://github.com/varnishcache-friends/vago/workflows/ci/badge.svg) 4 | [![Go Reference](https://pkg.go.dev/badge/github.com/varnishcache-friends/vago.svg)](https://pkg.go.dev/github.com/varnishcache-friends/vago) 5 | 6 | Go bindings for Varnish 6.0, 7.2 and 7.3. 7 | 8 | Older Varnish versions are not supported. 9 | 10 | ## Requirements 11 | 12 | To build this package you will need: 13 | 14 | * pkg-config 15 | * libvarnishapi-dev 16 | 17 | You will also need to set PKG_CONFIG_PATH to the directory where `varnishapi.pc` 18 | is located before running `go get`. For example: 19 | 20 | ``` 21 | export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig 22 | ``` 23 | 24 | ## Installation 25 | 26 | ``` 27 | go get github.com/varnishcache-friends/vago 28 | ``` 29 | 30 | ## Examples 31 | 32 | Same as running `varnishlog -g raw`: 33 | 34 | ```go 35 | package main 36 | 37 | import ( 38 | "fmt" 39 | "log" 40 | 41 | "github.com/varnishcache-friends/vago" 42 | ) 43 | 44 | func main() { 45 | // Open the default Varnish Shared Memory file 46 | c := vago.Config{} 47 | v, err := vago.Open(&c) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | v.Log("", vago.RAW, vago.COPT_TAIL|vago.COPT_BATCH, func(vxid uint32, tag, _type, data string) int { 52 | fmt.Printf("%10d %-14s %s %s\n", vxid, tag, _type, data) 53 | // -1 : Stop after it finds the first record 54 | // >= 0 : Nothing to do but wait 55 | return 0 56 | }) 57 | v.Close() 58 | } 59 | ``` 60 | 61 | Same for `varnishstat -1`: 62 | 63 | ```go 64 | package main 65 | 66 | import ( 67 | "fmt" 68 | "log" 69 | 70 | "github.com/varnishcache-friends/vago" 71 | ) 72 | 73 | func main() { 74 | // Open the default Varnish Shared Memory file 75 | c := vago.Config{} 76 | v, err := vago.Open(&c) 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | stats := v.Stats() 81 | for field, value := range stats { 82 | fmt.Printf("%-35s\t%12d\n", field, value) 83 | } 84 | v.Close() 85 | } 86 | ``` 87 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/varnishcache-friends/vago 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /handles.go: -------------------------------------------------------------------------------- 1 | // The MIT License 2 | // 3 | // Copyright (c) 2013 The git2go contributors 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | package vago 24 | 25 | /* 26 | #include 27 | */ 28 | import "C" 29 | 30 | import ( 31 | "sync" 32 | "unsafe" 33 | ) 34 | 35 | type handleList struct { 36 | sync.RWMutex 37 | handles map[unsafe.Pointer]interface{} 38 | } 39 | 40 | func newHandle() *handleList { 41 | return &handleList{ 42 | handles: make(map[unsafe.Pointer]interface{}), 43 | } 44 | } 45 | 46 | func (h *handleList) track(ptr interface{}) unsafe.Pointer { 47 | handle := C.malloc(1) 48 | h.Lock() 49 | h.handles[handle] = ptr 50 | h.Unlock() 51 | return handle 52 | } 53 | 54 | func (h *handleList) untrack(handle unsafe.Pointer) { 55 | h.Lock() 56 | delete(h.handles, handle) 57 | C.free(handle) 58 | h.Unlock() 59 | } 60 | 61 | func (h *handleList) get(handle unsafe.Pointer) interface{} { 62 | h.RLock() 63 | defer h.RUnlock() 64 | ptr, ok := h.handles[handle] 65 | if !ok { 66 | panic("invalid handle") 67 | } 68 | return ptr 69 | } 70 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | // Package vago 2 | 3 | package vago 4 | 5 | /* 6 | #cgo pkg-config: varnishapi 7 | #cgo LDFLAGS: -lvarnishapi -lm 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int dispatchCallback(struct VSL_data *vsl, struct VSL_transaction **trans, void *priv); 17 | */ 18 | import "C" 19 | 20 | import ( 21 | "encoding/binary" 22 | "errors" 23 | "time" 24 | "unsafe" 25 | ) 26 | 27 | const ( 28 | lenmask = 0xffff 29 | clientmarker = uint32(1) << 30 30 | backendmarker = uint32(1) << 31 31 | identmask = ^(uint32(3) << 30) 32 | // Cursor options 33 | COPT_TAIL = 1 << 0 34 | COPT_BATCH = 1 << 1 35 | COPT_TAILSTOP = 1 << 2 36 | // VSM status bitmap 37 | vsmWrkRestarted = 1 << 11 38 | ) 39 | 40 | var ( 41 | ErrAbandoned = errors.New("log abandoned") 42 | ErrOverrun = errors.New("log overrun") 43 | ) 44 | 45 | type ErrVSL string 46 | 47 | func (e ErrVSL) Error() string { return string(e) } 48 | 49 | // LogCallback defines a callback function. 50 | // It's used by Log. 51 | type LogCallback func(vxid uint32, tag, _type, data string) int 52 | 53 | // Log calls the given callback for any transactions matching the query 54 | // and grouping. 55 | func (v *Varnish) Log(query string, grouping uint32, copt uint, logCallback LogCallback) error { 56 | v.vsl = C.VSL_New() 57 | handle := ptrHandles.track(logCallback) 58 | defer ptrHandles.untrack(handle) 59 | if grouping > max { 60 | grouping = VXID 61 | } 62 | if query != "" { 63 | cs := C.CString(query) 64 | defer C.free(unsafe.Pointer(cs)) 65 | v.vslq = C.VSLQ_New(v.vsl, nil, grouping, cs) 66 | } else { 67 | v.vslq = C.VSLQ_New(v.vsl, nil, grouping, nil) 68 | } 69 | if v.vslq == nil { 70 | return ErrVSL(C.GoString(C.VSL_Error(v.vsl))) 71 | } 72 | hasCursor := -1 73 | DispatchLoop: 74 | for v.alive() { 75 | if v.vsm != nil && (C.VSM_Status(v.vsm)&vsmWrkRestarted) != 0 { 76 | if hasCursor < 1 { 77 | C.VSLQ_SetCursor(v.vslq, nil) 78 | hasCursor = 0 79 | } 80 | } 81 | if v.vsm != nil && hasCursor < 1 { 82 | // Reconnect VSM 83 | v.cursor = C.VSL_CursorVSM(v.vsl, v.vsm, C.uint(copt)) 84 | if v.cursor == nil { 85 | C.VSL_ResetError(v.vsl) 86 | continue 87 | } 88 | hasCursor = 1 89 | C.VSLQ_SetCursor(v.vslq, &v.cursor) 90 | } 91 | i := C.VSLQ_Dispatch(v.vslq, 92 | (*C.VSLQ_dispatch_f)(C.dispatchCallback), 93 | handle) 94 | switch i { 95 | case 1: 96 | // Call again 97 | continue 98 | case 0: 99 | // Nothing to do but wait 100 | time.Sleep(10 * time.Millisecond) 101 | continue 102 | case -1: 103 | // EOF 104 | break DispatchLoop 105 | case -2: 106 | // Abandoned 107 | if !v.vslReattach { 108 | return ErrAbandoned 109 | } 110 | // Re-acquire the log cursor 111 | C.VSLQ_SetCursor(v.vslq, nil) 112 | hasCursor = 0 113 | default: 114 | // Overrun 115 | return ErrOverrun 116 | } 117 | } 118 | 119 | return nil 120 | } 121 | 122 | // dispatchCallback walks through the transaction and calls a function of 123 | // type LogCallback. 124 | // 125 | //export dispatchCallback 126 | func dispatchCallback(vsl *C.struct_VSL_data, pt **C.struct_VSL_transaction, handle unsafe.Pointer) C.int { 127 | tx := uintptr(unsafe.Pointer(pt)) 128 | var _type string 129 | logCallback := ptrHandles.get(handle) 130 | for { 131 | if tx == 0 { 132 | break 133 | } 134 | t := ((**C.struct_VSL_transaction)(unsafe.Pointer(tx))) 135 | if *t == nil { 136 | break 137 | } 138 | for { 139 | i := C.VSL_Next((*t).c) 140 | if i < 0 { 141 | return C.int(i) 142 | } 143 | if i == 0 { 144 | break 145 | } 146 | if C.VSL_Match(vsl, (*t).c) == 0 { 147 | continue 148 | } 149 | s1 := cui32tosl((*t).c.rec.ptr, 8) 150 | tag := C.GoString(C.VSL_tags[s1[0]>>24]) 151 | vxid := s1[1] & identmask 152 | length := C.int(s1[0] & lenmask) 153 | switch { 154 | case s1[1]&(clientmarker) != 0: 155 | _type = "c" 156 | case s1[1]&(backendmarker) != 0: 157 | _type = "b" 158 | default: 159 | _type = "-" 160 | } 161 | s2 := cui32tosl((*t).c.rec.ptr, (length+2)*4) 162 | data := ui32tostr(&s2[2], length) 163 | ret := logCallback.(LogCallback)(vxid, tag, _type, data) 164 | if ret != 0 { 165 | return C.int(ret) 166 | } 167 | } 168 | tx += unsafe.Sizeof(t) 169 | } 170 | return 0 171 | } 172 | 173 | // Convert C.uint32_t to slice of uint32 174 | func cui32tosl(ptr *C.uint32_t, length C.int) []uint32 { 175 | b := C.GoBytes(unsafe.Pointer(ptr), length) 176 | s := make([]uint32, length/4) 177 | for i := range s { 178 | s[i] = binary.LittleEndian.Uint32(b[i*4 : (i+1)*4]) 179 | } 180 | return s 181 | } 182 | 183 | // Convert uint32 to string 184 | func ui32tostr(val *uint32, length C.int) string { 185 | return C.GoStringN((*C.char)(unsafe.Pointer(val)), length-1) 186 | } 187 | -------------------------------------------------------------------------------- /stats.go: -------------------------------------------------------------------------------- 1 | // Package vago 2 | 3 | package vago 4 | 5 | /* 6 | #cgo pkg-config: varnishapi 7 | #cgo LDFLAGS: -lvarnishapi -lm 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int listCallback(void *priv, struct VSC_point *); 17 | */ 18 | import "C" 19 | 20 | import ( 21 | "unsafe" 22 | ) 23 | 24 | // Stats returns a map with all stat counters and their values. 25 | func (v *Varnish) Stats() map[string]uint64 { 26 | items := make(map[string]uint64) 27 | handle := ptrHandles.track(items) 28 | defer ptrHandles.untrack(handle) 29 | C.VSC_Iter(v.vsc, v.vsm, 30 | (*C.VSC_iter_f)(C.listCallback), 31 | handle) 32 | return items 33 | } 34 | 35 | // Stat takes a Varnish stat field and returns its value and true if found, 36 | // 0 and false otherwise. 37 | func (v *Varnish) Stat(s string) (uint64, bool) { 38 | stats := v.Stats() 39 | value, ok := stats[s] 40 | return value, ok 41 | } 42 | 43 | // do_list_cb() 44 | // 45 | //export listCallback 46 | func listCallback(handle unsafe.Pointer, pt *C.struct_VSC_point) C.int { 47 | priv := ptrHandles.get(handle) 48 | if pt == nil || priv == nil { 49 | return 1 50 | } 51 | name := C.GoString(pt.name) 52 | value := *(*uint64)(unsafe.Pointer(pt.ptr)) 53 | items, ok := priv.(map[string]uint64) 54 | if !ok { 55 | return 1 56 | } 57 | items[name] = value 58 | return 0 59 | } 60 | -------------------------------------------------------------------------------- /vago.go: -------------------------------------------------------------------------------- 1 | // Package vago 2 | 3 | package vago 4 | 5 | /* 6 | #cgo pkg-config: varnishapi 7 | #cgo LDFLAGS: -lvarnishapi -lm 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | */ 16 | import "C" 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "sync" 22 | "time" 23 | "unsafe" 24 | ) 25 | 26 | const ( 27 | // Grouping mode 28 | RAW = C.VSL_g_raw 29 | VXID = C.VSL_g_vxid 30 | REQ = C.VSL_g_request 31 | SESS = C.VSL_g_session 32 | max = C.VSL_g__MAX 33 | ) 34 | 35 | const ( 36 | _ tribool = iota 37 | Yes 38 | No 39 | ) 40 | 41 | // A Varnish struct represents a handler for Varnish Shared Memory and 42 | // Varnish Shared Log. 43 | type Varnish struct { 44 | vsc *C.struct_vsc 45 | vsm *C.struct_vsm 46 | vsl *C.struct_VSL_data 47 | vslq *C.struct_VSLQ 48 | cursor *C.struct_VSL_cursor 49 | mu sync.Mutex 50 | done chan struct{} 51 | closed bool 52 | vslReattach bool 53 | } 54 | 55 | // Config parameters to connect to a Varnish instance. 56 | type Config struct { 57 | // Path to Varnish Shared Memory file 58 | Path string 59 | // VSM connection timeout in milliseconds 60 | // a negative value for no timeout 61 | Timeout time.Duration 62 | // Whether to reacquire the to the log 63 | // Values can be Yes or No. Default Yes 64 | VslReattach tribool 65 | } 66 | 67 | type tribool uint8 68 | 69 | var ptrHandles *handleList 70 | 71 | func init() { 72 | ptrHandles = newHandle() 73 | } 74 | 75 | // Open opens a Varnish Shared Memory file. If successful, returns a new 76 | // Varnish. 77 | func Open(c *Config) (*Varnish, error) { 78 | v := &Varnish{closed: true} 79 | v.vsm = C.VSM_New() 80 | if v.vsm == nil { 81 | return nil, errors.New(C.GoString(C.VSM_Error(v.vsm))) 82 | } 83 | v.vsc = C.VSC_New() 84 | if v.vsc == nil { 85 | defer v.Close() 86 | return nil, errors.New(C.GoString(C.VSM_Error(v.vsm))) 87 | } 88 | if c.Path != "" { 89 | cs := C.CString(c.Path) 90 | defer C.free(unsafe.Pointer(cs)) 91 | arg := C.CString("n") 92 | defer C.free(unsafe.Pointer(arg)) 93 | if C.VSM_Arg(v.vsm, *arg, cs) != 1 { 94 | defer v.Close() 95 | return nil, errors.New(C.GoString(C.VSM_Error(v.vsm))) 96 | } 97 | } 98 | var cs *C.char 99 | switch { 100 | case c.Timeout < 0: 101 | cs = C.CString("off") 102 | default: 103 | cs = C.CString(fmt.Sprintf("%d", c.Timeout/1000)) 104 | } 105 | defer C.free(unsafe.Pointer(cs)) 106 | arg := C.CString("t") 107 | defer C.free(unsafe.Pointer(arg)) 108 | if C.VSM_Arg(v.vsm, *arg, cs) != 1 { 109 | defer v.Close() 110 | return nil, errors.New(C.GoString(C.VSM_Error(v.vsm))) 111 | } 112 | if C.VSM_Attach(v.vsm, -1) != 0 { 113 | defer v.Close() 114 | return nil, errors.New(C.GoString(C.VSM_Error(v.vsm))) 115 | } 116 | 117 | v.done = make(chan struct{}) 118 | v.closed = false 119 | if c.VslReattach != No { 120 | v.vslReattach = true 121 | } 122 | 123 | return v, nil 124 | } 125 | 126 | func (v *Varnish) alive() bool { 127 | select { 128 | case <-v.done: 129 | return false 130 | default: 131 | return true 132 | } 133 | } 134 | 135 | // Stop stops processing Varnish events. 136 | func (v *Varnish) Stop() { 137 | v.mu.Lock() 138 | defer v.mu.Unlock() 139 | 140 | if !v.closed { 141 | close(v.done) 142 | v.closed = true 143 | } 144 | } 145 | 146 | // Close closes and unmaps the Varnish Shared Memory. 147 | func (v *Varnish) Close() { 148 | v.Stop() 149 | if v.vslq != nil { 150 | C.VSLQ_Delete(&v.vslq) 151 | } 152 | if v.vsl != nil { 153 | C.VSL_Delete(v.vsl) 154 | } 155 | if v.vsc != nil { 156 | C.VSC_Destroy(&v.vsc, v.vsm) 157 | } 158 | if v.vsm != nil { 159 | C.VSM_Destroy(&v.vsm) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /vago_test.go: -------------------------------------------------------------------------------- 1 | package vago 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestOpenFail(t *testing.T) { 11 | c := Config{ 12 | Path: "/nonexistent", 13 | } 14 | _, err := Open(&c) 15 | if err == nil { 16 | t.Fatal("Expected non-nil") 17 | } 18 | } 19 | 20 | func TestOpenOK(t *testing.T) { 21 | c := Config{} 22 | v, err := Open(&c) 23 | if err != nil { 24 | t.Log(err) 25 | t.Fatal("Expected nil") 26 | } 27 | v.Close() 28 | } 29 | 30 | func TestOpenFailedTimeout(t *testing.T) { 31 | c := Config{ 32 | Timeout: 2000, 33 | Path: "/nonexistent", 34 | } 35 | start := time.Now() 36 | _, err := Open(&c) 37 | end := time.Now() 38 | if err == nil { 39 | t.Fatal("Expected non-nil") 40 | } 41 | if end.Sub(start) < c.Timeout*time.Millisecond { 42 | t.Fatal("Expected timeout >= c.Timeout") 43 | } 44 | } 45 | 46 | func TestLog(t *testing.T) { 47 | c := Config{} 48 | v, err := Open(&c) 49 | if err != nil { 50 | t.Fatal("Expected nil") 51 | } 52 | defer v.Close() 53 | err = v.Log("", RAW, COPT_TAIL|COPT_BATCH, func(vxid uint32, tag, _type, data string) int { 54 | if vxid == 0 && tag == "CLI" && _type == "-" && strings.Contains(data, "PONG") { 55 | return -1 56 | } 57 | return 0 58 | }) 59 | if err != nil { 60 | t.Fatal("Expected nil") 61 | } 62 | } 63 | 64 | func TestLogGoroutineClose(t *testing.T) { 65 | var wg sync.WaitGroup 66 | var err error 67 | var v *Varnish 68 | c := Config{} 69 | v, err = Open(&c) 70 | if err != nil { 71 | t.Fatal("Expected nil") 72 | } 73 | wg.Add(1) 74 | go func(v *Varnish) { 75 | defer wg.Done() 76 | err = v.Log("", RAW, COPT_TAIL|COPT_BATCH, func(vxid uint32, tag, _type, data string) int { 77 | return -1 78 | }) 79 | }(v) 80 | time.Sleep(10 * time.Millisecond) 81 | v.Stop() 82 | wg.Wait() 83 | v.Close() 84 | if err != nil { 85 | t.Fatal("Expected nil") 86 | } 87 | } 88 | 89 | func TestInvalidQuery(t *testing.T) { 90 | c := Config{} 91 | v, err := Open(&c) 92 | if err != nil { 93 | t.Fatal("Expected nil") 94 | } 95 | defer v.Close() 96 | err = v.Log("nonexistent", RAW, COPT_TAIL|COPT_BATCH, func(vxid uint32, tag, _type, data string) int { 97 | return -1 98 | }) 99 | if _, ok := err.(ErrVSL); !ok { 100 | t.Fatal("Expected ErrVSL") 101 | } 102 | } 103 | 104 | func TestStats(t *testing.T) { 105 | c := Config{} 106 | v, err := Open(&c) 107 | if err != nil { 108 | t.Fatal("Expected nil") 109 | } 110 | defer v.Close() 111 | items := v.Stats() 112 | if len(items) == 0 { 113 | t.Fatal("Expected map with elements") 114 | } 115 | } 116 | 117 | func TestStatFail(t *testing.T) { 118 | c := Config{} 119 | v, err := Open(&c) 120 | if err != nil { 121 | t.Fatal("Expected nil") 122 | } 123 | defer v.Close() 124 | if _, ok := v.Stat("foo"); ok { 125 | t.Fatal("Expected false") 126 | } 127 | } 128 | 129 | func TestStatOK(t *testing.T) { 130 | c := Config{} 131 | v, err := Open(&c) 132 | if err != nil { 133 | t.Fatal("Expected nil") 134 | } 135 | defer v.Close() 136 | if _, ok := v.Stat("MAIN.uptime"); !ok { 137 | t.Fatal("Expected some value") 138 | } 139 | } 140 | --------------------------------------------------------------------------------