mirror of
https://github.com/octoleo/restic.git
synced 2024-11-30 00:33:57 +00:00
Merge pull request #1767 from restic/update-blazer
Update github.com/kurin/blazer
This commit is contained in:
commit
e42b7db008
6
Gopkg.lock
generated
6
Gopkg.lock
generated
@ -93,9 +93,9 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/kurin/blazer"
|
name = "github.com/kurin/blazer"
|
||||||
packages = ["b2","base","internal/b2types","internal/blog"]
|
packages = ["b2","base","internal/b2assets","internal/b2types","internal/blog","x/window"]
|
||||||
revision = "cd0304efa98725679cf68422cefa328d3d96f2f4"
|
revision = "b7c9cf27cae3aec98c2caaeb5181608bfe05b17c"
|
||||||
version = "v0.3.0"
|
version = "v0.3.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/marstr/guid"
|
name = "github.com/marstr/guid"
|
||||||
|
56
vendor/github.com/kurin/blazer/b2/b2.go
generated
vendored
56
vendor/github.com/kurin/blazer/b2/b2.go
generated
vendored
@ -45,7 +45,7 @@ type Client struct {
|
|||||||
slock sync.Mutex
|
slock sync.Mutex
|
||||||
sWriters map[string]*Writer
|
sWriters map[string]*Writer
|
||||||
sReaders map[string]*Reader
|
sReaders map[string]*Reader
|
||||||
sMethods map[string]int
|
sMethods []methodCounter
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates and returns a new Client with valid B2 service account
|
// NewClient creates and returns a new Client with valid B2 service account
|
||||||
@ -55,7 +55,12 @@ func NewClient(ctx context.Context, account, key string, opts ...ClientOption) (
|
|||||||
backend: &beRoot{
|
backend: &beRoot{
|
||||||
b2i: &b2Root{},
|
b2i: &b2Root{},
|
||||||
},
|
},
|
||||||
sMethods: make(map[string]int),
|
sMethods: []methodCounter{
|
||||||
|
newMethodCounter(time.Minute, time.Second),
|
||||||
|
newMethodCounter(time.Minute*5, time.Second),
|
||||||
|
newMethodCounter(time.Hour, time.Minute),
|
||||||
|
newMethodCounter(0, 0), // forever
|
||||||
|
},
|
||||||
}
|
}
|
||||||
opts = append(opts, client(c))
|
opts = append(opts, client(c))
|
||||||
if err := c.backend.authorizeAccount(ctx, account, key, opts...); err != nil {
|
if err := c.backend.authorizeAccount(ctx, account, key, opts...); err != nil {
|
||||||
@ -73,6 +78,26 @@ type clientOptions struct {
|
|||||||
userAgents []string
|
userAgents []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for testing
|
||||||
|
func (c clientOptions) eq(o clientOptions) bool {
|
||||||
|
if c.client != o.client ||
|
||||||
|
c.transport != o.transport ||
|
||||||
|
c.failSomeUploads != o.failSomeUploads ||
|
||||||
|
c.expireTokens != o.expireTokens ||
|
||||||
|
c.capExceeded != o.capExceeded {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(c.userAgents) != len(o.userAgents) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range c.userAgents {
|
||||||
|
if c.userAgents[i] != o.userAgents[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// A ClientOption allows callers to adjust various per-client settings.
|
// A ClientOption allows callers to adjust various per-client settings.
|
||||||
type ClientOption func(*clientOptions)
|
type ClientOption func(*clientOptions)
|
||||||
|
|
||||||
@ -131,17 +156,30 @@ type clientTransport struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ct *clientTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
func (ct *clientTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
method := r.Header.Get("X-Blazer-Method")
|
m := r.Header.Get("X-Blazer-Method")
|
||||||
if method != "" && ct.client != nil {
|
|
||||||
ct.client.slock.Lock()
|
|
||||||
ct.client.sMethods[method]++
|
|
||||||
ct.client.slock.Unlock()
|
|
||||||
}
|
|
||||||
t := ct.rt
|
t := ct.rt
|
||||||
if t == nil {
|
if t == nil {
|
||||||
t = http.DefaultTransport
|
t = http.DefaultTransport
|
||||||
}
|
}
|
||||||
return t.RoundTrip(r)
|
b := time.Now()
|
||||||
|
resp, err := t.RoundTrip(r)
|
||||||
|
e := time.Now()
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
if m != "" && ct.client != nil {
|
||||||
|
ct.client.slock.Lock()
|
||||||
|
m := method{
|
||||||
|
name: m,
|
||||||
|
duration: e.Sub(b),
|
||||||
|
status: resp.StatusCode,
|
||||||
|
}
|
||||||
|
for _, counter := range ct.client.sMethods {
|
||||||
|
counter.record(m)
|
||||||
|
}
|
||||||
|
ct.client.slock.Unlock()
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket is a reference to a B2 bucket.
|
// Bucket is a reference to a B2 bucket.
|
||||||
|
4
vendor/github.com/kurin/blazer/b2/backend.go
generated
vendored
4
vendor/github.com/kurin/blazer/b2/backend.go
generated
vendored
@ -37,6 +37,7 @@ type beRootInterface interface {
|
|||||||
type beRoot struct {
|
type beRoot struct {
|
||||||
account, key string
|
account, key string
|
||||||
b2i b2RootInterface
|
b2i b2RootInterface
|
||||||
|
options []ClientOption
|
||||||
}
|
}
|
||||||
|
|
||||||
type beBucketInterface interface {
|
type beBucketInterface interface {
|
||||||
@ -156,13 +157,14 @@ func (r *beRoot) authorizeAccount(ctx context.Context, account, key string, opts
|
|||||||
}
|
}
|
||||||
r.account = account
|
r.account = account
|
||||||
r.key = key
|
r.key = key
|
||||||
|
r.options = opts
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return withBackoff(ctx, r, f)
|
return withBackoff(ctx, r, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *beRoot) reauthorizeAccount(ctx context.Context) error {
|
func (r *beRoot) reauthorizeAccount(ctx context.Context) error {
|
||||||
return r.authorizeAccount(ctx, r.account, r.key)
|
return r.authorizeAccount(ctx, r.account, r.key, r.options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *beRoot) createBucket(ctx context.Context, name, btype string, info map[string]string, rules []LifecycleRule) (beBucketInterface, error) {
|
func (r *beRoot) createBucket(ctx context.Context, name, btype string, info map[string]string, rules []LifecycleRule) (beBucketInterface, error) {
|
||||||
|
68
vendor/github.com/kurin/blazer/b2/integration_test.go
generated
vendored
68
vendor/github.com/kurin/blazer/b2/integration_test.go
generated
vendored
@ -25,7 +25,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -734,26 +733,7 @@ func TestWriteEmpty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type rtCounter struct {
|
|
||||||
rt http.RoundTripper
|
|
||||||
trips int
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *rtCounter) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
rt.Lock()
|
|
||||||
defer rt.Unlock()
|
|
||||||
rt.trips++
|
|
||||||
return rt.rt.RoundTrip(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAttrsNoRoundtrip(t *testing.T) {
|
func TestAttrsNoRoundtrip(t *testing.T) {
|
||||||
rt := &rtCounter{rt: defaultTransport}
|
|
||||||
defaultTransport = rt
|
|
||||||
defer func() {
|
|
||||||
defaultTransport = rt.rt
|
|
||||||
}()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -774,7 +754,10 @@ func TestAttrsNoRoundtrip(t *testing.T) {
|
|||||||
t.Fatalf("unexpected objects: got %d, want 1", len(objs))
|
t.Fatalf("unexpected objects: got %d, want 1", len(objs))
|
||||||
}
|
}
|
||||||
|
|
||||||
trips := rt.trips
|
var trips int
|
||||||
|
for range bucket.c.Status().table()["1m"] {
|
||||||
|
trips += 1
|
||||||
|
}
|
||||||
attrs, err := objs[0].Attrs(ctx)
|
attrs, err := objs[0].Attrs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -783,8 +766,12 @@ func TestAttrsNoRoundtrip(t *testing.T) {
|
|||||||
t.Errorf("got the wrong object: got %q, want %q", attrs.Name, smallFileName)
|
t.Errorf("got the wrong object: got %q, want %q", attrs.Name, smallFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if trips != rt.trips {
|
var newTrips int
|
||||||
t.Errorf("Attrs() should not have caused any net traffic, but it did: old %d, new %d", trips, rt.trips)
|
for range bucket.c.Status().table()["1m"] {
|
||||||
|
newTrips += 1
|
||||||
|
}
|
||||||
|
if trips != newTrips {
|
||||||
|
t.Errorf("Attrs() should not have caused any net traffic, but it did: old %d, new %d", trips, newTrips)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -837,8 +824,8 @@ func TestSmallUploadsFewRoundtrips(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
si := bucket.c.Status()
|
si := bucket.c.Status()
|
||||||
getURL := si.MethodCalls["b2_get_upload_url"]
|
getURL := si.RPCs[0].CountByMethod()["b2_get_upload_url"]
|
||||||
uploadFile := si.MethodCalls["b2_upload_file"]
|
uploadFile := si.RPCs[0].CountByMethod()["b2_upload_file"]
|
||||||
if getURL >= uploadFile {
|
if getURL >= uploadFile {
|
||||||
t.Errorf("too many calls to b2_get_upload_url")
|
t.Errorf("too many calls to b2_get_upload_url")
|
||||||
}
|
}
|
||||||
@ -882,6 +869,37 @@ func TestListUnfinishedLargeFiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReauthPreservesOptions(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
bucket, done := startLiveTest(ctx, t)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
var first []ClientOption
|
||||||
|
opts := bucket.r.(*beRoot).options
|
||||||
|
for _, o := range opts {
|
||||||
|
first = append(first, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bucket.r.reauthorizeAccount(ctx); err != nil {
|
||||||
|
t.Fatalf("reauthorizeAccount: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
second := bucket.r.(*beRoot).options
|
||||||
|
if len(second) != len(first) {
|
||||||
|
t.Fatalf("options mismatch: got %d options, wanted %d", len(second), len(first))
|
||||||
|
}
|
||||||
|
|
||||||
|
var f, s clientOptions
|
||||||
|
for i := range first {
|
||||||
|
first[i](&f)
|
||||||
|
second[i](&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.eq(s) {
|
||||||
|
t.Errorf("options mismatch: got %v, want %v", s, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type object struct {
|
type object struct {
|
||||||
o *Object
|
o *Object
|
||||||
err error
|
err error
|
||||||
|
153
vendor/github.com/kurin/blazer/b2/monitor.go
generated
vendored
153
vendor/github.com/kurin/blazer/b2/monitor.go
generated
vendored
@ -14,13 +14,83 @@
|
|||||||
|
|
||||||
package b2
|
package b2
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kurin/blazer/internal/b2assets"
|
||||||
|
"github.com/kurin/blazer/x/window"
|
||||||
|
)
|
||||||
|
|
||||||
// StatusInfo reports information about a client.
|
// StatusInfo reports information about a client.
|
||||||
type StatusInfo struct {
|
type StatusInfo struct {
|
||||||
|
// Writers contains the status of all current uploads with progress.
|
||||||
Writers map[string]*WriterStatus
|
Writers map[string]*WriterStatus
|
||||||
|
|
||||||
|
// Readers contains the status of all current downloads with progress.
|
||||||
Readers map[string]*ReaderStatus
|
Readers map[string]*ReaderStatus
|
||||||
MethodCalls map[string]int
|
|
||||||
|
// RPCs contains information about recently made RPC calls over the last
|
||||||
|
// minute, five minutes, hour, and for all time.
|
||||||
|
RPCs map[time.Duration]MethodList
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodList is an accumulation of RPC calls that have been made over a given
|
||||||
|
// period of time.
|
||||||
|
type MethodList []method
|
||||||
|
|
||||||
|
// CountByMethod returns the total RPC calls made per method.
|
||||||
|
func (ml MethodList) CountByMethod() map[string]int {
|
||||||
|
r := make(map[string]int)
|
||||||
|
for i := range ml {
|
||||||
|
r[ml[i].name]++
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type method struct {
|
||||||
|
name string
|
||||||
|
duration time.Duration
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
type methodCounter struct {
|
||||||
|
d time.Duration
|
||||||
|
w *window.Window
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc methodCounter) record(m method) {
|
||||||
|
mc.w.Insert([]method{m})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc methodCounter) retrieve() MethodList {
|
||||||
|
ms := mc.w.Reduce()
|
||||||
|
return MethodList(ms.([]method))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMethodCounter(d, res time.Duration) methodCounter {
|
||||||
|
r := func(i, j interface{}) interface{} {
|
||||||
|
a, ok := i.([]method)
|
||||||
|
if !ok {
|
||||||
|
a = nil
|
||||||
|
}
|
||||||
|
b, ok := j.([]method)
|
||||||
|
if !ok {
|
||||||
|
b = nil
|
||||||
|
}
|
||||||
|
for _, m := range b {
|
||||||
|
a = append(a, m)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return methodCounter{
|
||||||
|
d: d,
|
||||||
|
w: window.New(d, res, r),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriterStatus reports the status for each writer.
|
// WriterStatus reports the status for each writer.
|
||||||
@ -45,7 +115,7 @@ func (c *Client) Status() *StatusInfo {
|
|||||||
si := &StatusInfo{
|
si := &StatusInfo{
|
||||||
Writers: make(map[string]*WriterStatus),
|
Writers: make(map[string]*WriterStatus),
|
||||||
Readers: make(map[string]*ReaderStatus),
|
Readers: make(map[string]*ReaderStatus),
|
||||||
MethodCalls: make(map[string]int),
|
RPCs: make(map[time.Duration]MethodList),
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, w := range c.sWriters {
|
for name, w := range c.sWriters {
|
||||||
@ -56,13 +126,30 @@ func (c *Client) Status() *StatusInfo {
|
|||||||
si.Readers[name] = r.status()
|
si.Readers[name] = r.status()
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, n := range c.sMethods {
|
for _, c := range c.sMethods {
|
||||||
si.MethodCalls[name] = n
|
si.RPCs[c.d] = c.retrieve()
|
||||||
}
|
}
|
||||||
|
|
||||||
return si
|
return si
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (si *StatusInfo) table() map[string]map[string]int {
|
||||||
|
r := make(map[string]map[string]int)
|
||||||
|
for d, c := range si.RPCs {
|
||||||
|
for _, m := range c {
|
||||||
|
if _, ok := r[m.name]; !ok {
|
||||||
|
r[m.name] = make(map[string]int)
|
||||||
|
}
|
||||||
|
dur := "all time"
|
||||||
|
if d > 0 {
|
||||||
|
dur = d.String()
|
||||||
|
}
|
||||||
|
r[m.name][dur]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) addWriter(w *Writer) {
|
func (c *Client) addWriter(w *Writer) {
|
||||||
c.slock.Lock()
|
c.slock.Lock()
|
||||||
defer c.slock.Unlock()
|
defer c.slock.Unlock()
|
||||||
@ -106,3 +193,59 @@ func (c *Client) removeReader(r *Reader) {
|
|||||||
|
|
||||||
delete(c.sReaders, fmt.Sprintf("%s/%s", r.o.b.Name(), r.name))
|
delete(c.sReaders, fmt.Sprintf("%s/%s", r.o.b.Name(), r.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
funcMap = template.FuncMap{
|
||||||
|
"inc": func(i int) int { return i + 1 },
|
||||||
|
"lookUp": func(m map[string]int, s string) int { return m[s] },
|
||||||
|
"pRange": func(i int) string {
|
||||||
|
f := float64(i)
|
||||||
|
min := int(math.Pow(2, f)) - 1
|
||||||
|
max := min + int(math.Pow(2, f))
|
||||||
|
return fmt.Sprintf("%v - %v", time.Duration(min)*time.Millisecond, time.Duration(max)*time.Millisecond)
|
||||||
|
},
|
||||||
|
"methods": func(si *StatusInfo) []string {
|
||||||
|
methods := make(map[string]bool)
|
||||||
|
for _, ms := range si.RPCs {
|
||||||
|
for _, m := range ms {
|
||||||
|
methods[m.name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var names []string
|
||||||
|
for name := range methods {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
return names
|
||||||
|
},
|
||||||
|
"durations": func(si *StatusInfo) []string {
|
||||||
|
var ds []time.Duration
|
||||||
|
for d := range si.RPCs {
|
||||||
|
ds = append(ds, d)
|
||||||
|
}
|
||||||
|
sort.Slice(ds, func(i, j int) bool { return ds[i] < ds[j] })
|
||||||
|
var r []string
|
||||||
|
for _, d := range ds {
|
||||||
|
dur := "all time"
|
||||||
|
if d > 0 {
|
||||||
|
dur = d.String()
|
||||||
|
}
|
||||||
|
r = append(r, dur)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
},
|
||||||
|
"table": func(si *StatusInfo) map[string]map[string]int { return si.table() },
|
||||||
|
}
|
||||||
|
statusTemplate = template.Must(template.New("status").Funcs(funcMap).Parse(string(b2assets.MustAsset("data/status.html"))))
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServeHTTP serves diagnostic information about the current state of the
|
||||||
|
// client; essentially everything available from Client.Status()
|
||||||
|
//
|
||||||
|
// ServeHTTP satisfies the http.Handler interface. This means that a Client
|
||||||
|
// can be passed directly to a path via http.Handle (or on a custom ServeMux or
|
||||||
|
// a custom http.Server).
|
||||||
|
func (c *Client) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
info := c.Status()
|
||||||
|
statusTemplate.Execute(rw, info)
|
||||||
|
}
|
||||||
|
2
vendor/github.com/kurin/blazer/base/base.go
generated
vendored
2
vendor/github.com/kurin/blazer/base/base.go
generated
vendored
@ -42,7 +42,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
APIBase = "https://api.backblazeb2.com"
|
APIBase = "https://api.backblazeb2.com"
|
||||||
DefaultUserAgent = "blazer/0.3.0"
|
DefaultUserAgent = "blazer/0.3.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type b2err struct {
|
type b2err struct {
|
||||||
|
237
vendor/github.com/kurin/blazer/internal/b2assets/b2assets.go
generated
vendored
Normal file
237
vendor/github.com/kurin/blazer/internal/b2assets/b2assets.go
generated
vendored
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
// Code generated by go-bindata.
|
||||||
|
// sources:
|
||||||
|
// data/status.html
|
||||||
|
// DO NOT EDIT!
|
||||||
|
|
||||||
|
package b2assets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||||
|
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = io.Copy(&buf, gz)
|
||||||
|
clErr := gz.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||||
|
}
|
||||||
|
if clErr != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type asset struct {
|
||||||
|
bytes []byte
|
||||||
|
info os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type bindataFileInfo struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
mode os.FileMode
|
||||||
|
modTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fi bindataFileInfo) Name() string {
|
||||||
|
return fi.name
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) Size() int64 {
|
||||||
|
return fi.size
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||||
|
return fi.mode
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) ModTime() time.Time {
|
||||||
|
return fi.modTime
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) IsDir() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _dataStatusHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x93\x41\x6f\xe3\x20\x10\x85\xef\xf9\x15\xb3\x56\x8e\x51\x90\x73\x5c\x4d\xb8\xec\xee\x79\xa3\xaa\x52\xd5\x23\x36\xa3\x60\x09\x43\x84\x71\x9a\xc8\xe2\xbf\x57\x18\x83\xa3\xb6\x87\x5e\x7b\xf2\x98\xf7\xe6\xf1\xbe\x03\xf8\xeb\xef\xff\x3f\xcf\xaf\xa7\x7f\xa0\x7c\xaf\xf9\x06\xf3\x87\x84\xe4\x1b\x00\xf4\x9d\xd7\xc4\x9b\x03\xb4\xba\x23\xe3\x61\xf0\xc2\x8f\x03\xb2\x74\xbe\x41\x96\x9c\xd8\x58\x79\x8f\x0b\xd3\xb4\xed\xc9\x2b\x2b\x07\xf8\x7d\x84\x3c\xee\x43\x48\x9a\x1c\x9d\xf0\x9d\x35\xb3\xba\xfe\x14\xdd\x8b\x46\x53\xd4\xd2\x90\xce\x51\xd5\xbc\xb5\xa3\xf1\xd0\xdc\xa1\xb5\x92\x90\xa9\x3a\xb5\x8b\xae\x38\xc5\x65\x27\xcc\x99\x60\xb9\x3e\x66\xe4\x26\x73\x48\x74\xbb\x64\x8d\xa3\xe4\xa5\x69\x08\xc8\xbc\xcc\x52\xc9\xc9\xed\xe6\xa4\x52\x75\xc9\x5a\x43\x3a\x23\xe9\x06\x4b\xf1\x7c\x79\xf1\x7f\xcc\x26\x23\x73\x1b\x96\xeb\xac\xa7\xc8\x0a\x50\x64\x1e\x2f\xda\x0a\x39\x64\xda\x87\x6e\x46\xf4\xb4\x83\xed\x55\xe8\xd8\x6e\xff\xe2\x3a\x4f\xae\x70\xaa\x03\x9f\xa6\x64\x82\x58\x40\x1d\x3e\xc1\x75\x72\x07\xdb\x8b\xb3\xe7\x99\xee\x2a\xf4\xfe\xe4\xec\xd9\xd1\xb0\x02\x46\xb4\x36\x3a\x43\x00\xbc\x2c\x2a\x5c\x85\x1e\xe9\x58\x4d\xd3\xbc\x1d\x42\x05\xbd\xb8\x1d\xab\xba\xe2\xc8\xb2\x89\x63\xe3\x80\x7d\x05\xfd\x80\xaa\x6a\x2e\xed\x9b\xf9\x26\xe1\x13\x09\xf9\xa3\x08\x91\xa5\x17\x81\x2c\xbd\xa8\xf7\x00\x00\x00\xff\xff\xd4\xf0\x90\xb4\x69\x03\x00\x00")
|
||||||
|
|
||||||
|
func dataStatusHtmlBytes() ([]byte, error) {
|
||||||
|
return bindataRead(
|
||||||
|
_dataStatusHtml,
|
||||||
|
"data/status.html",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataStatusHtml() (*asset, error) {
|
||||||
|
bytes, err := dataStatusHtmlBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := bindataFileInfo{name: "data/status.html", size: 873, mode: os.FileMode(436), modTime: time.Unix(1520578750, 0)}
|
||||||
|
a := &asset{bytes: bytes, info: info}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asset loads and returns the asset for the given name.
|
||||||
|
// It returns an error if the asset could not be found or
|
||||||
|
// could not be loaded.
|
||||||
|
func Asset(name string) ([]byte, error) {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
if f, ok := _bindata[cannonicalName]; ok {
|
||||||
|
a, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||||
|
}
|
||||||
|
return a.bytes, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustAsset is like Asset but panics when Asset would return an error.
|
||||||
|
// It simplifies safe initialization of global variables.
|
||||||
|
func MustAsset(name string) []byte {
|
||||||
|
a, err := Asset(name)
|
||||||
|
if err != nil {
|
||||||
|
panic("asset: Asset(" + name + "): " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetInfo loads and returns the asset info for the given name.
|
||||||
|
// It returns an error if the asset could not be found or
|
||||||
|
// could not be loaded.
|
||||||
|
func AssetInfo(name string) (os.FileInfo, error) {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
if f, ok := _bindata[cannonicalName]; ok {
|
||||||
|
a, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||||
|
}
|
||||||
|
return a.info, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetNames returns the names of the assets.
|
||||||
|
func AssetNames() []string {
|
||||||
|
names := make([]string, 0, len(_bindata))
|
||||||
|
for name := range _bindata {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||||
|
var _bindata = map[string]func() (*asset, error){
|
||||||
|
"data/status.html": dataStatusHtml,
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetDir returns the file names below a certain
|
||||||
|
// directory embedded in the file by go-bindata.
|
||||||
|
// For example if you run go-bindata on data/... and data contains the
|
||||||
|
// following hierarchy:
|
||||||
|
// data/
|
||||||
|
// foo.txt
|
||||||
|
// img/
|
||||||
|
// a.png
|
||||||
|
// b.png
|
||||||
|
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||||
|
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||||
|
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||||
|
// AssetDir("") will return []string{"data"}.
|
||||||
|
func AssetDir(name string) ([]string, error) {
|
||||||
|
node := _bintree
|
||||||
|
if len(name) != 0 {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
pathList := strings.Split(cannonicalName, "/")
|
||||||
|
for _, p := range pathList {
|
||||||
|
node = node.Children[p]
|
||||||
|
if node == nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.Func != nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
rv := make([]string, 0, len(node.Children))
|
||||||
|
for childName := range node.Children {
|
||||||
|
rv = append(rv, childName)
|
||||||
|
}
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bintree struct {
|
||||||
|
Func func() (*asset, error)
|
||||||
|
Children map[string]*bintree
|
||||||
|
}
|
||||||
|
var _bintree = &bintree{nil, map[string]*bintree{
|
||||||
|
"data": &bintree{nil, map[string]*bintree{
|
||||||
|
"status.html": &bintree{dataStatusHtml, map[string]*bintree{}},
|
||||||
|
}},
|
||||||
|
}}
|
||||||
|
|
||||||
|
// RestoreAsset restores an asset under the given directory
|
||||||
|
func RestoreAsset(dir, name string) error {
|
||||||
|
data, err := Asset(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info, err := AssetInfo(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreAssets restores an asset under the given directory recursively
|
||||||
|
func RestoreAssets(dir, name string) error {
|
||||||
|
children, err := AssetDir(name)
|
||||||
|
// File
|
||||||
|
if err != nil {
|
||||||
|
return RestoreAsset(dir, name)
|
||||||
|
}
|
||||||
|
// Dir
|
||||||
|
for _, child := range children {
|
||||||
|
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _filePath(dir, name string) string {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||||
|
}
|
||||||
|
|
36
vendor/github.com/kurin/blazer/internal/b2assets/data/status.html
generated
vendored
Normal file
36
vendor/github.com/kurin/blazer/internal/b2assets/data/status.html
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>b2 client status</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{$methods := methods .}}
|
||||||
|
{{$durations := durations .}}
|
||||||
|
{{$table := table .}}
|
||||||
|
<h1>count by code</h1>
|
||||||
|
<table>
|
||||||
|
{{range $method := $methods}}
|
||||||
|
<tr>
|
||||||
|
<td>{{$method}}</td>
|
||||||
|
{{range $duration := $durations}}
|
||||||
|
<td>{{index $table $method $duration}}</td>
|
||||||
|
{{end}}
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</table>
|
||||||
|
<h1>uploads</h1>
|
||||||
|
{{range $name, $val := .Writers}}
|
||||||
|
<h2>{{ $name }}</h2>
|
||||||
|
{{range $id, $prog := $val.Progress}}
|
||||||
|
{{inc $id}} <progress value="{{$prog}}" max="1"></progress><br />
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
<h1>downloads</h1>
|
||||||
|
{{range $name, $val := .Readers}}
|
||||||
|
<h2>{{ $name }}</h2>
|
||||||
|
{{range $id, $prog := $val.Progress}}
|
||||||
|
{{inc $id}} <progress value="{{$prog}}" max="1"></progress><br />
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
18
vendor/github.com/kurin/blazer/internal/b2assets/gen.go
generated
vendored
Normal file
18
vendor/github.com/kurin/blazer/internal/b2assets/gen.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2018, Google
|
||||||
|
//
|
||||||
|
// 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 b2assets contains data required by other libraries in blazer.
|
||||||
|
package b2assets
|
||||||
|
|
||||||
|
//go:generate go-bindata -pkg $GOPACKAGE -o b2assets.go data/
|
63
vendor/github.com/kurin/blazer/x/window/accum_test.go
generated
vendored
Normal file
63
vendor/github.com/kurin/blazer/x/window/accum_test.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2018, Google
|
||||||
|
//
|
||||||
|
// 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 window_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kurin/blazer/x/window"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Accumulator struct {
|
||||||
|
w *window.Window
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Accumulator) Add(s string) {
|
||||||
|
a.w.Insert([]string{s})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Accumulator) All() []string {
|
||||||
|
v := a.w.Reduce()
|
||||||
|
return v.([]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccum(size time.Duration) Accumulator {
|
||||||
|
r := func(i, j interface{}) interface{} {
|
||||||
|
a, ok := i.([]string)
|
||||||
|
if !ok {
|
||||||
|
a = nil
|
||||||
|
}
|
||||||
|
b, ok := j.([]string)
|
||||||
|
if !ok {
|
||||||
|
b = nil
|
||||||
|
}
|
||||||
|
for _, s := range b {
|
||||||
|
a = append(a, s)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return Accumulator{w: window.New(size, time.Second, r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_accumulator() {
|
||||||
|
a := NewAccum(time.Minute)
|
||||||
|
a.Add("this")
|
||||||
|
a.Add("is")
|
||||||
|
a.Add("that")
|
||||||
|
fmt.Printf("total: %v\n", a.All())
|
||||||
|
// Output:
|
||||||
|
// total: [this is that]
|
||||||
|
}
|
60
vendor/github.com/kurin/blazer/x/window/counter_test.go
generated
vendored
Normal file
60
vendor/github.com/kurin/blazer/x/window/counter_test.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2018, Google
|
||||||
|
//
|
||||||
|
// 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 window_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kurin/blazer/x/window"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Counter struct {
|
||||||
|
w *window.Window
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Counter) Add() {
|
||||||
|
c.w.Insert(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Counter) Count() int {
|
||||||
|
v := c.w.Reduce()
|
||||||
|
return v.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(size time.Duration) Counter {
|
||||||
|
r := func(i, j interface{}) interface{} {
|
||||||
|
a, ok := i.(int)
|
||||||
|
if !ok {
|
||||||
|
a = 0
|
||||||
|
}
|
||||||
|
b, ok := j.(int)
|
||||||
|
if !ok {
|
||||||
|
b = 0
|
||||||
|
}
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
return Counter{w: window.New(size, time.Second, r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_counter() {
|
||||||
|
c := New(time.Minute)
|
||||||
|
c.Add()
|
||||||
|
c.Add()
|
||||||
|
c.Add()
|
||||||
|
fmt.Printf("total: %d\n", c.Count())
|
||||||
|
// Output:
|
||||||
|
// total: 3
|
||||||
|
}
|
151
vendor/github.com/kurin/blazer/x/window/window.go
generated
vendored
Normal file
151
vendor/github.com/kurin/blazer/x/window/window.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// Copyright 2018, Google
|
||||||
|
//
|
||||||
|
// 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 window provides a type for efficiently recording events as they
|
||||||
|
// occur over a given span of time. Events added to the window will remain
|
||||||
|
// until the time expires.
|
||||||
|
package window
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Window efficiently records events that have occurred over a span of time
|
||||||
|
// extending from some fixed interval ago to now. Events that pass beyond this
|
||||||
|
// horizon effectively "fall off" the back of the window.
|
||||||
|
type Window struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
events []interface{}
|
||||||
|
res time.Duration
|
||||||
|
last time.Time
|
||||||
|
reduce Reducer
|
||||||
|
forever bool
|
||||||
|
e interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Reducer should take two values from the window and combine them into a
|
||||||
|
// third value that will be stored in the window. The values i or j may be
|
||||||
|
// nil. The underlying types for both arguments and the output should be
|
||||||
|
// identical.
|
||||||
|
//
|
||||||
|
// If the reducer is any kind of slice or list, then data usage will grow
|
||||||
|
// linearly with the number of events added to the window.
|
||||||
|
//
|
||||||
|
// Reducer will be called on its own output: Reducer(Reducer(x, y), z).
|
||||||
|
type Reducer func(i, j interface{}) interface{}
|
||||||
|
|
||||||
|
// New returns an initialized window for events over the given duration at the
|
||||||
|
// given resolution. Windows with tight resolution (i.e., small values for
|
||||||
|
// that argument) will be more accurate, at the cost of some memory.
|
||||||
|
//
|
||||||
|
// A size of 0 means "forever"; old events will never be removed.
|
||||||
|
func New(size, resolution time.Duration, r Reducer) *Window {
|
||||||
|
if size > 0 {
|
||||||
|
return &Window{
|
||||||
|
res: resolution,
|
||||||
|
events: make([]interface{}, size/resolution),
|
||||||
|
reduce: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Window{
|
||||||
|
forever: true,
|
||||||
|
reduce: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) bucket(now time.Time) int {
|
||||||
|
nanos := now.UnixNano()
|
||||||
|
abs := nanos / int64(w.res)
|
||||||
|
return int(abs) % len(w.events)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sweep keeps the window valid. It needs to be called from every method that
|
||||||
|
// views or updates the window, and the caller needs to hold the mutex.
|
||||||
|
func (w *Window) sweep(now time.Time) {
|
||||||
|
if w.forever {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
w.last = now
|
||||||
|
}()
|
||||||
|
|
||||||
|
b := w.bucket(now)
|
||||||
|
p := w.bucket(w.last)
|
||||||
|
|
||||||
|
if b == p && now.Sub(w.last) <= w.res {
|
||||||
|
// We're in the same bucket as the previous sweep, so all buckets are
|
||||||
|
// valid.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if now.Sub(w.last) > w.res*time.Duration(len(w.events)) {
|
||||||
|
// We've gone longer than this window measures since the last sweep, just
|
||||||
|
// zero the thing and have done.
|
||||||
|
for i := range w.events {
|
||||||
|
w.events[i] = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expire all invalid buckets. This means buckets not seen since the
|
||||||
|
// previous sweep and now, including the current bucket but not including the
|
||||||
|
// previous bucket.
|
||||||
|
old := int(w.last.UnixNano()) / int(w.res)
|
||||||
|
new := int(now.UnixNano()) / int(w.res)
|
||||||
|
for i := old + 1; i <= new; i++ {
|
||||||
|
b := i % len(w.events)
|
||||||
|
w.events[b] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert adds the given event.
|
||||||
|
func (w *Window) Insert(e interface{}) {
|
||||||
|
w.insertAt(time.Now(), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) insertAt(t time.Time, e interface{}) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.forever {
|
||||||
|
w.e = w.reduce(w.e, e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.sweep(t)
|
||||||
|
w.events[w.bucket(t)] = w.reduce(w.events[w.bucket(t)], e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce runs the window's reducer over the valid values and returns the
|
||||||
|
// result.
|
||||||
|
func (w *Window) Reduce() interface{} {
|
||||||
|
return w.reducedAt(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) reducedAt(t time.Time) interface{} {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.forever {
|
||||||
|
return w.e
|
||||||
|
}
|
||||||
|
|
||||||
|
w.sweep(t)
|
||||||
|
var n interface{}
|
||||||
|
for i := range w.events {
|
||||||
|
n = w.reduce(n, w.events[i])
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
88
vendor/github.com/kurin/blazer/x/window/window_test.go
generated
vendored
Normal file
88
vendor/github.com/kurin/blazer/x/window/window_test.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// Copyright 2018, Google
|
||||||
|
//
|
||||||
|
// 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 window
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type epair struct {
|
||||||
|
e interface{}
|
||||||
|
t time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func adder(i, j interface{}) interface{} {
|
||||||
|
a, ok := i.(int)
|
||||||
|
if !ok {
|
||||||
|
a = 0
|
||||||
|
}
|
||||||
|
b, ok := j.(int)
|
||||||
|
if !ok {
|
||||||
|
b = 0
|
||||||
|
}
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWindows(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
size, dur time.Duration
|
||||||
|
incs []epair
|
||||||
|
look time.Time
|
||||||
|
reduce Reducer
|
||||||
|
want interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
size: time.Minute,
|
||||||
|
dur: time.Second,
|
||||||
|
incs: []epair{
|
||||||
|
// year, month, day, hour, min, sec, nano
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), e: 1},
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 1, 0, time.UTC), e: 1},
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 2, 0, time.UTC), e: 1},
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 3, 0, time.UTC), e: 1},
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 4, 0, time.UTC), e: 1},
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 5, 0, time.UTC), e: 1},
|
||||||
|
},
|
||||||
|
look: time.Date(2000, 1, 1, 0, 1, 0, 0, time.UTC),
|
||||||
|
want: 5,
|
||||||
|
reduce: adder,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
incs: []epair{
|
||||||
|
// year, month, day, hour, min, sec, nano
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), e: 1},
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 1, 0, time.UTC), e: 1},
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 2, 0, time.UTC), e: 1},
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 3, 0, time.UTC), e: 1},
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 4, 0, time.UTC), e: 1},
|
||||||
|
{t: time.Date(2000, 1, 1, 0, 0, 5, 0, time.UTC), e: 1},
|
||||||
|
},
|
||||||
|
want: 6,
|
||||||
|
reduce: adder,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range table {
|
||||||
|
w := New(e.size, e.dur, e.reduce)
|
||||||
|
for _, inc := range e.incs {
|
||||||
|
w.insertAt(inc.t, inc.e)
|
||||||
|
}
|
||||||
|
ct := w.reducedAt(e.look)
|
||||||
|
if ct != e.want {
|
||||||
|
t.Errorf("reducedAt(%v) got %v, want %v", e.look, ct, e.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user