PLT-3753 Added Segment analytics (#3972)

This commit is contained in:
David Lu
2016-09-06 18:51:27 -04:00
committed by enahum
parent 47d77d2589
commit 51501f920c
44 changed files with 2704 additions and 41 deletions

4
vendor/github.com/jehiah/go-strftime/README.md generated vendored Normal file
View File

@@ -0,0 +1,4 @@
go-strftime
===========
go implementation of strftime

71
vendor/github.com/jehiah/go-strftime/strftime.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
// go implementation of strftime
package strftime
import (
"strings"
"time"
)
// taken from time/format.go
var conversion = map[rune]string {
/*stdLongMonth */ 'B':"January",
/*stdMonth */ 'b': "Jan",
// stdNumMonth */ 'm': "1",
/*stdZeroMonth */ 'm': "01",
/*stdLongWeekDay */ 'A': "Monday",
/*stdWeekDay */ 'a': "Mon",
// stdDay */ 'd': "2",
// stdUnderDay */ 'd': "_2",
/*stdZeroDay */ 'd': "02",
/*stdHour */ 'H': "15",
// stdHour12 */ 'I': "3",
/*stdZeroHour12 */ 'I': "03",
// stdMinute */ 'M': "4",
/*stdZeroMinute */ 'M': "04",
// stdSecond */ 'S': "5",
/*stdZeroSecond */ 'S': "05",
/*stdLongYear */ 'Y': "2006",
/*stdYear */ 'y': "06",
/*stdPM */ 'p': "PM",
// stdpm */ 'p': "pm",
/*stdTZ */ 'Z': "MST",
// stdISO8601TZ */ 'z': "Z0700", // prints Z for UTC
// stdISO8601ColonTZ */ 'z': "Z07:00", // prints Z for UTC
/*stdNumTZ */ 'z': "-0700", // always numeric
// stdNumShortTZ */ 'b': "-07", // always numeric
// stdNumColonTZ */ 'b': "-07:00", // always numeric
}
// This is an alternative to time.Format because no one knows
// what date 040305 is supposed to create when used as a 'layout' string
// this takes standard strftime format options. For a complete list
// of format options see http://strftime.org/
func Format(format string, t time.Time) string {
retval := make([]byte, 0, len(format))
for i, ni := 0, 0; i < len(format); i = ni + 2 {
ni = strings.IndexByte(format[i:], '%')
if ni < 0 {
ni = len(format)
} else {
ni += i
}
retval = append(retval, []byte(format[i:ni])...)
if ni + 1 < len(format) {
c := format[ni + 1]
if c == '%' {
retval = append(retval, '%')
} else {
if layoutCmd, ok := conversion[rune(c)]; ok {
retval = append(retval, []byte(t.Format(layoutCmd))...)
} else {
retval = append(retval, '%', c)
}
}
} else {
if ni < len(format) {
retval = append(retval, '%')
}
}
}
return string(retval)
}

40
vendor/github.com/jehiah/go-strftime/strftime_test.go generated vendored Normal file
View File

@@ -0,0 +1,40 @@
package strftime
import (
"time"
"fmt"
"testing"
)
func ExampleFormat() {
t := time.Unix(1340244776, 0)
utc, _ := time.LoadLocation("UTC")
t = t.In(utc)
fmt.Println(Format("%Y-%m-%d %H:%M:%S", t))
// Output:
// 2012-06-21 02:12:56
}
func TestNoLeadingPercentSign(t *testing.T) {
tm := time.Unix(1340244776, 0)
utc, _ := time.LoadLocation("UTC")
tm = tm.In(utc)
result := Format("aaabbb0123456789%Y", tm)
if result != "aaabbb01234567892012" {
t.Logf("%s != %s", result, "aaabbb01234567892012")
t.Fail()
}
}
func TestUnsupported(t *testing.T) {
tm := time.Unix(1340244776, 0)
utc, _ := time.LoadLocation("UTC")
tm = tm.In(utc)
result := Format("%0%1%%%2", tm)
if result != "%0%1%%2" {
t.Logf("%s != %s", result, "%0%1%%2")
t.Fail()
}
}

View File

@@ -0,0 +1,17 @@
{
"ImportPath": "github.com/segmentio/analytics-go",
"GoVersion": "go1.4.2",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/jehiah/go-strftime",
"Rev": "834e15c05a45371503440cc195bbd05c9a0968d9"
},
{
"ImportPath": "github.com/xtgo/uuid",
"Rev": "a0b114877d4caeffbd7f87e3757c17fce570fea7"
}
]
}

View File

@@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View File

@@ -0,0 +1,2 @@
/pkg
/bin

View File

@@ -0,0 +1,22 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

View File

@@ -0,0 +1,4 @@
go-strftime
===========
go implementation of strftime

View File

@@ -0,0 +1,71 @@
// go implementation of strftime
package strftime
import (
"strings"
"time"
)
// taken from time/format.go
var conversion = map[rune]string {
/*stdLongMonth */ 'B':"January",
/*stdMonth */ 'b': "Jan",
// stdNumMonth */ 'm': "1",
/*stdZeroMonth */ 'm': "01",
/*stdLongWeekDay */ 'A': "Monday",
/*stdWeekDay */ 'a': "Mon",
// stdDay */ 'd': "2",
// stdUnderDay */ 'd': "_2",
/*stdZeroDay */ 'd': "02",
/*stdHour */ 'H': "15",
// stdHour12 */ 'I': "3",
/*stdZeroHour12 */ 'I': "03",
// stdMinute */ 'M': "4",
/*stdZeroMinute */ 'M': "04",
// stdSecond */ 'S': "5",
/*stdZeroSecond */ 'S': "05",
/*stdLongYear */ 'Y': "2006",
/*stdYear */ 'y': "06",
/*stdPM */ 'p': "PM",
// stdpm */ 'p': "pm",
/*stdTZ */ 'Z': "MST",
// stdISO8601TZ */ 'z': "Z0700", // prints Z for UTC
// stdISO8601ColonTZ */ 'z': "Z07:00", // prints Z for UTC
/*stdNumTZ */ 'z': "-0700", // always numeric
// stdNumShortTZ */ 'b': "-07", // always numeric
// stdNumColonTZ */ 'b': "-07:00", // always numeric
}
// This is an alternative to time.Format because no one knows
// what date 040305 is supposed to create when used as a 'layout' string
// this takes standard strftime format options. For a complete list
// of format options see http://strftime.org/
func Format(format string, t time.Time) string {
retval := make([]byte, 0, len(format))
for i, ni := 0, 0; i < len(format); i = ni + 2 {
ni = strings.IndexByte(format[i:], '%')
if ni < 0 {
ni = len(format)
} else {
ni += i
}
retval = append(retval, []byte(format[i:ni])...)
if ni + 1 < len(format) {
c := format[ni + 1]
if c == '%' {
retval = append(retval, '%')
} else {
if layoutCmd, ok := conversion[rune(c)]; ok {
retval = append(retval, []byte(t.Format(layoutCmd))...)
} else {
retval = append(retval, '%', c)
}
}
} else {
if ni < len(format) {
retval = append(retval, '%')
}
}
}
return string(retval)
}

View File

@@ -0,0 +1,40 @@
package strftime
import (
"time"
"fmt"
"testing"
)
func ExampleFormat() {
t := time.Unix(1340244776, 0)
utc, _ := time.LoadLocation("UTC")
t = t.In(utc)
fmt.Println(Format("%Y-%m-%d %H:%M:%S", t))
// Output:
// 2012-06-21 02:12:56
}
func TestNoLeadingPercentSign(t *testing.T) {
tm := time.Unix(1340244776, 0)
utc, _ := time.LoadLocation("UTC")
tm = tm.In(utc)
result := Format("aaabbb0123456789%Y", tm)
if result != "aaabbb01234567892012" {
t.Logf("%s != %s", result, "aaabbb01234567892012")
t.Fail()
}
}
func TestUnsupported(t *testing.T) {
tm := time.Unix(1340244776, 0)
utc, _ := time.LoadLocation("UTC")
tm = tm.In(utc)
result := Format("%0%1%%%2", tm)
if result != "%0%1%%2" {
t.Logf("%s != %s", result, "%0%1%%2")
t.Fail()
}
}

View File

@@ -0,0 +1,5 @@
# This source file refers to The gocql Authors for copyright purposes.
Christoph Hack <christoph@tux21b.org>
Jonathan Rudenberg <jonathan@titanous.com>
Thorsten von Eicken <tve@rightscale.com>

View File

@@ -0,0 +1,27 @@
Copyright (c) 2012 The gocql Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,204 @@
// Copyright (c) 2012 The gocql Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package uuid can be used to generate and parse universally unique
// identifiers, a standardized format in the form of a 128 bit number.
//
// http://tools.ietf.org/html/rfc4122
package uuid
import (
"crypto/rand"
"encoding/hex"
"errors"
"io"
"net"
"strconv"
"time"
)
type UUID [16]byte
var hardwareAddr []byte
const (
VariantNCSCompat = 0
VariantIETF = 2
VariantMicrosoft = 6
VariantFuture = 7
)
func init() {
if interfaces, err := net.Interfaces(); err == nil {
for _, i := range interfaces {
if i.Flags&net.FlagLoopback == 0 && len(i.HardwareAddr) > 0 {
hardwareAddr = i.HardwareAddr
break
}
}
}
if hardwareAddr == nil {
// If we failed to obtain the MAC address of the current computer,
// we will use a randomly generated 6 byte sequence instead and set
// the multicast bit as recommended in RFC 4122.
hardwareAddr = make([]byte, 6)
_, err := io.ReadFull(rand.Reader, hardwareAddr)
if err != nil {
panic(err)
}
hardwareAddr[0] = hardwareAddr[0] | 0x01
}
}
// Parse parses a 32 digit hexadecimal number (that might contain hyphens)
// representing an UUID.
func Parse(input string) (UUID, error) {
var u UUID
j := 0
for i := 0; i < len(input); i++ {
b := input[i]
switch {
default:
fallthrough
case j == 32:
goto err
case b == '-':
continue
case '0' <= b && b <= '9':
b -= '0'
case 'a' <= b && b <= 'f':
b -= 'a' - 10
case 'A' <= b && b <= 'F':
b -= 'A' - 10
}
u[j/2] |= b << byte(^j&1<<2)
j++
}
if j == 32 {
return u, nil
}
err:
return UUID{}, errors.New("invalid UUID " + strconv.Quote(input))
}
// FromBytes converts a raw byte slice to an UUID. It will panic if the slice
// isn't exactly 16 bytes long.
func FromBytes(input []byte) UUID {
var u UUID
if len(input) != 16 {
panic("UUIDs must be exactly 16 bytes long")
}
copy(u[:], input)
return u
}
// NewRandom generates a totally random UUID (version 4) as described in
// RFC 4122.
func NewRandom() UUID {
var u UUID
io.ReadFull(rand.Reader, u[:])
u[6] &= 0x0F // clear version
u[6] |= 0x40 // set version to 4 (random uuid)
u[8] &= 0x3F // clear variant
u[8] |= 0x80 // set to IETF variant
return u
}
var timeBase = time.Date(1582, time.October, 15, 0, 0, 0, 0, time.UTC).Unix()
// NewTime generates a new time based UUID (version 1) as described in RFC
// 4122. This UUID contains the MAC address of the node that generated the
// UUID, a timestamp and a sequence number.
func NewTime() UUID {
var u UUID
now := time.Now().In(time.UTC)
t := uint64(now.Unix()-timeBase)*10000000 + uint64(now.Nanosecond()/100)
u[0], u[1], u[2], u[3] = byte(t>>24), byte(t>>16), byte(t>>8), byte(t)
u[4], u[5] = byte(t>>40), byte(t>>32)
u[6], u[7] = byte(t>>56)&0x0F, byte(t>>48)
var clockSeq [2]byte
io.ReadFull(rand.Reader, clockSeq[:])
u[8] = clockSeq[1]
u[9] = clockSeq[0]
copy(u[10:], hardwareAddr)
u[6] |= 0x10 // set version to 1 (time based uuid)
u[8] &= 0x3F // clear variant
u[8] |= 0x80 // set to IETF variant
return u
}
// String returns the UUID in it's canonical form, a 32 digit hexadecimal
// number in the form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
func (u UUID) String() string {
buf := [36]byte{8: '-', 13: '-', 18: '-', 23: '-'}
hex.Encode(buf[0:], u[0:4])
hex.Encode(buf[9:], u[4:6])
hex.Encode(buf[14:], u[6:8])
hex.Encode(buf[19:], u[8:10])
hex.Encode(buf[24:], u[10:])
return string(buf[:])
}
// Bytes returns the raw byte slice for this UUID. A UUID is always 128 bits
// (16 bytes) long.
func (u UUID) Bytes() []byte {
return u[:]
}
// Variant returns the variant of this UUID. This package will only generate
// UUIDs in the IETF variant.
func (u UUID) Variant() int {
x := u[8]
switch byte(0) {
case x & 0x80:
return VariantNCSCompat
case x & 0x40:
return VariantIETF
case x & 0x20:
return VariantMicrosoft
}
return VariantFuture
}
// Version extracts the version of this UUID variant. The RFC 4122 describes
// five kinds of UUIDs.
func (u UUID) Version() int {
return int(u[6] & 0xF0 >> 4)
}
// Node extracts the MAC address of the node who generated this UUID. It will
// return nil if the UUID is not a time based UUID (version 1).
func (u UUID) Node() []byte {
if u.Version() != 1 {
return nil
}
return u[10:]
}
// Timestamp extracts the timestamp information from a time based UUID
// (version 1).
func (u UUID) Timestamp() uint64 {
if u.Version() != 1 {
return 0
}
return uint64(u[0])<<24 + uint64(u[1])<<16 + uint64(u[2])<<8 +
uint64(u[3]) + uint64(u[4])<<40 + uint64(u[5])<<32 +
uint64(u[7])<<48 + uint64(u[6]&0x0F)<<56
}
// Time is like Timestamp, except that it returns a time.Time.
func (u UUID) Time() time.Time {
t := u.Timestamp()
if t == 0 {
return time.Time{}
}
sec := t / 10000000
nsec := t - sec
return time.Unix(int64(sec)+timeBase, int64(nsec))
}

View File

@@ -0,0 +1,102 @@
// Copyright (c) 2012 The gocql Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"testing"
)
func TestNil(t *testing.T) {
var uuid UUID
want, got := "00000000-0000-0000-0000-000000000000", uuid.String()
if want != got {
t.Fatalf("TestNil: expected %q got %q", want, got)
}
}
var tests = []struct {
input string
variant int
version int
}{
{"b4f00409-cef8-4822-802c-deb20704c365", VariantIETF, 4},
{"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
{"00000000-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
{"3051a8d7-aea7-1801-e0bf-bc539dd60cf3", VariantFuture, 1},
{"3051a8d7-aea7-2801-e0bf-bc539dd60cf3", VariantFuture, 2},
{"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 3},
{"3051a8d7-aea7-4801-e0bf-bc539dd60cf3", VariantFuture, 4},
{"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 5},
{"d0e817e1-e4b1-1801-3fe6-b4b60ccecf9d", VariantNCSCompat, 0},
{"d0e817e1-e4b1-1801-bfe6-b4b60ccecf9d", VariantIETF, 1},
{"d0e817e1-e4b1-1801-dfe6-b4b60ccecf9d", VariantMicrosoft, 0},
{"d0e817e1-e4b1-1801-ffe6-b4b60ccecf9d", VariantFuture, 0},
}
func TestPredefined(t *testing.T) {
for i := range tests {
uuid, err := Parse(tests[i].input)
if err != nil {
t.Errorf("Parse #%d: %v", i, err)
continue
}
if str := uuid.String(); str != tests[i].input {
t.Errorf("String #%d: expected %q got %q", i, tests[i].input, str)
continue
}
if variant := uuid.Variant(); variant != tests[i].variant {
t.Errorf("Variant #%d: expected %d got %d", i, tests[i].variant, variant)
}
if tests[i].variant == VariantIETF {
if version := uuid.Version(); version != tests[i].version {
t.Errorf("Version #%d: expected %d got %d", i, tests[i].version, version)
}
}
}
}
func TestNewRandom(t *testing.T) {
for i := 0; i < 20; i++ {
uuid := NewRandom()
if variant := uuid.Variant(); variant != VariantIETF {
t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant)
}
if version := uuid.Version(); version != 4 {
t.Errorf("wrong version. expected %d got %d", 4, version)
}
}
}
func TestNewTime(t *testing.T) {
var node []byte
timestamp := uint64(0)
for i := 0; i < 20; i++ {
uuid := NewTime()
if variant := uuid.Variant(); variant != VariantIETF {
t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant)
}
if version := uuid.Version(); version != 1 {
t.Errorf("wrong version. expected %d got %d", 1, version)
}
if n := uuid.Node(); !bytes.Equal(n, node) && i > 0 {
t.Errorf("wrong node. expected %x, got %x", node, n)
} else if i == 0 {
node = n
}
ts := uuid.Timestamp()
if ts < timestamp {
t.Errorf("timestamps must grow")
}
timestamp = ts
}
}

67
vendor/github.com/segmentio/analytics-go/History.md generated vendored Normal file
View File

@@ -0,0 +1,67 @@
v2.1.1 / 2016-04-26
===================
* Fix blocking the goroutine when Close is the first call.
* Fix blocking the goroutine when the message queue fills up.
v2.1.0 / 2015-12-28
===================
* Add ability to set custom timestamps for messages.
* Add ability to set a custom `net/http` client.
* Add ability to set a custom logger.
* Fix edge case when client would try to upload no messages.
* Properly upload in-flight messages when client is asked to shutdown.
* Add ability to set `.integrations` field on messages.
* Fix resource leak with interval ticker after shutdown.
* Add retries and back-off when uploading messages.
* Add ability to set custom flush interval.
v2.0.0 / 2015-02-03
===================
* rewrite with breaking API changes
v1.2.0 / 2014-09-03
==================
* add public .Flush() method
* rename .Stop() to .Close()
v1.1.0 / 2014-09-02
==================
* add client.Stop() to flash/wait. Closes #7
v1.0.0 / 2014-08-26
==================
* fix response close
* change comments to be more go-like
* change uuid libraries
0.1.2 / 2014-06-11
==================
* add runnable example
* fix: close body
0.1.1 / 2014-05-31
==================
* refactor locking
0.1.0 / 2014-05-22
==================
* replace Debug option with debug package
0.0.2 / 2014-05-20
==================
* add .Start()
* add mutexes
* rename BufferSize to FlushAt and FlushInterval to FlushAfter
* lower FlushInterval to 5 seconds
* lower BufferSize to 20 to match other clients

10
vendor/github.com/segmentio/analytics-go/Makefile generated vendored Normal file
View File

@@ -0,0 +1,10 @@
vet:
@godep go vet ./...
build:
@godep go build
test:
@godep go test -race -cover ./...
.PHONY: vet build test

8
vendor/github.com/segmentio/analytics-go/Readme.md generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# analytics-go
Segment analytics client for Go. For additional documentation
visit [https://segment.com/docs/libraries/go](https://segment.com/docs/libraries/go/) or view the [godocs](http://godoc.org/github.com/segmentio/analytics-go).
## License
MIT

407
vendor/github.com/segmentio/analytics-go/analytics.go generated vendored Normal file
View File

@@ -0,0 +1,407 @@
package analytics
import (
"fmt"
"io/ioutil"
"os"
"sync"
"bytes"
"encoding/json"
"errors"
"log"
"net/http"
"time"
"github.com/jehiah/go-strftime"
"github.com/segmentio/backo-go"
"github.com/xtgo/uuid"
)
// Version of the client.
const Version = "2.1.0"
// Endpoint for the Segment API.
const Endpoint = "https://api.segment.io"
// DefaultContext of message batches.
var DefaultContext = map[string]interface{}{
"library": map[string]interface{}{
"name": "analytics-go",
"version": Version,
},
}
// Backoff policy.
var Backo = backo.DefaultBacko()
// Message interface.
type message interface {
setMessageId(string)
setTimestamp(string)
}
// Message fields common to all.
type Message struct {
Type string `json:"type,omitempty"`
MessageId string `json:"messageId,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
SentAt string `json:"sentAt,omitempty"`
}
// Batch message.
type Batch struct {
Context map[string]interface{} `json:"context,omitempty"`
Messages []interface{} `json:"batch"`
Message
}
// Identify message.
type Identify struct {
Context map[string]interface{} `json:"context,omitempty"`
Integrations map[string]interface{} `json:"integrations,omitempty"`
Traits map[string]interface{} `json:"traits,omitempty"`
AnonymousId string `json:"anonymousId,omitempty"`
UserId string `json:"userId,omitempty"`
Message
}
// Group message.
type Group struct {
Context map[string]interface{} `json:"context,omitempty"`
Integrations map[string]interface{} `json:"integrations,omitempty"`
Traits map[string]interface{} `json:"traits,omitempty"`
AnonymousId string `json:"anonymousId,omitempty"`
UserId string `json:"userId,omitempty"`
GroupId string `json:"groupId"`
Message
}
// Track message.
type Track struct {
Context map[string]interface{} `json:"context,omitempty"`
Integrations map[string]interface{} `json:"integrations,omitempty"`
Properties map[string]interface{} `json:"properties,omitempty"`
AnonymousId string `json:"anonymousId,omitempty"`
UserId string `json:"userId,omitempty"`
Event string `json:"event"`
Message
}
// Page message.
type Page struct {
Context map[string]interface{} `json:"context,omitempty"`
Integrations map[string]interface{} `json:"integrations,omitempty"`
Traits map[string]interface{} `json:"properties,omitempty"`
AnonymousId string `json:"anonymousId,omitempty"`
UserId string `json:"userId,omitempty"`
Category string `json:"category,omitempty"`
Name string `json:"name,omitempty"`
Message
}
// Alias message.
type Alias struct {
PreviousId string `json:"previousId"`
UserId string `json:"userId"`
Message
}
// Client which batches messages and flushes at the given Interval or
// when the Size limit is exceeded. Set Verbose to true to enable
// logging output.
type Client struct {
Endpoint string
// Interval represents the duration at which messages are flushed. It may be
// configured only before any messages are enqueued.
Interval time.Duration
Size int
Logger *log.Logger
Verbose bool
Client http.Client
key string
msgs chan interface{}
quit chan struct{}
shutdown chan struct{}
uid func() string
now func() time.Time
once sync.Once
wg sync.WaitGroup
// These synchronization primitives are used to control how many goroutines
// are spawned by the client for uploads.
upmtx sync.Mutex
upcond sync.Cond
upcount int
}
// New client with write key.
func New(key string) *Client {
c := &Client{
Endpoint: Endpoint,
Interval: 5 * time.Second,
Size: 250,
Logger: log.New(os.Stderr, "segment ", log.LstdFlags),
Verbose: false,
Client: *http.DefaultClient,
key: key,
msgs: make(chan interface{}, 100),
quit: make(chan struct{}),
shutdown: make(chan struct{}),
now: time.Now,
uid: uid,
}
c.upcond.L = &c.upmtx
return c
}
// Alias buffers an "alias" message.
func (c *Client) Alias(msg *Alias) error {
if msg.UserId == "" {
return errors.New("You must pass a 'userId'.")
}
if msg.PreviousId == "" {
return errors.New("You must pass a 'previousId'.")
}
msg.Type = "alias"
c.queue(msg)
return nil
}
// Page buffers an "page" message.
func (c *Client) Page(msg *Page) error {
if msg.UserId == "" && msg.AnonymousId == "" {
return errors.New("You must pass either an 'anonymousId' or 'userId'.")
}
msg.Type = "page"
c.queue(msg)
return nil
}
// Group buffers an "group" message.
func (c *Client) Group(msg *Group) error {
if msg.GroupId == "" {
return errors.New("You must pass a 'groupId'.")
}
if msg.UserId == "" && msg.AnonymousId == "" {
return errors.New("You must pass either an 'anonymousId' or 'userId'.")
}
msg.Type = "group"
c.queue(msg)
return nil
}
// Identify buffers an "identify" message.
func (c *Client) Identify(msg *Identify) error {
if msg.UserId == "" && msg.AnonymousId == "" {
return errors.New("You must pass either an 'anonymousId' or 'userId'.")
}
msg.Type = "identify"
c.queue(msg)
return nil
}
// Track buffers an "track" message.
func (c *Client) Track(msg *Track) error {
if msg.Event == "" {
return errors.New("You must pass 'event'.")
}
if msg.UserId == "" && msg.AnonymousId == "" {
return errors.New("You must pass either an 'anonymousId' or 'userId'.")
}
msg.Type = "track"
c.queue(msg)
return nil
}
func (c *Client) startLoop() {
go c.loop()
}
// Queue message.
func (c *Client) queue(msg message) {
c.once.Do(c.startLoop)
msg.setMessageId(c.uid())
msg.setTimestamp(timestamp(c.now()))
c.msgs <- msg
}
// Close and flush metrics.
func (c *Client) Close() error {
c.once.Do(c.startLoop)
c.quit <- struct{}{}
close(c.msgs)
<-c.shutdown
return nil
}
func (c *Client) sendAsync(msgs []interface{}) {
c.upmtx.Lock()
for c.upcount == 1000 {
c.upcond.Wait()
}
c.upcount++
c.upmtx.Unlock()
c.wg.Add(1)
go func() {
err := c.send(msgs)
if err != nil {
c.logf(err.Error())
}
c.upmtx.Lock()
c.upcount--
c.upcond.Signal()
c.upmtx.Unlock()
c.wg.Done()
}()
}
// Send batch request.
func (c *Client) send(msgs []interface{}) error {
if len(msgs) == 0 {
return nil
}
batch := new(Batch)
batch.Messages = msgs
batch.MessageId = c.uid()
batch.SentAt = timestamp(c.now())
batch.Context = DefaultContext
b, err := json.Marshal(batch)
if err != nil {
return fmt.Errorf("error marshalling msgs: %s", err)
}
for i := 0; i < 10; i++ {
if err = c.upload(b); err == nil {
return nil
}
Backo.Sleep(i)
}
return err
}
// Upload serialized batch message.
func (c *Client) upload(b []byte) error {
url := c.Endpoint + "/v1/batch"
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
if err != nil {
return fmt.Errorf("error creating request: %s", err)
}
req.Header.Add("User-Agent", "analytics-go (version: "+Version+")")
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Content-Length", string(len(b)))
req.SetBasicAuth(c.key, "")
res, err := c.Client.Do(req)
if err != nil {
return fmt.Errorf("error sending request: %s", err)
}
defer res.Body.Close()
if res.StatusCode < 400 {
c.verbose("response %s", res.Status)
return nil
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("error reading response body: %s", err)
}
return fmt.Errorf("response %s: %d %s", res.Status, res.StatusCode, string(body))
}
// Batch loop.
func (c *Client) loop() {
var msgs []interface{}
tick := time.NewTicker(c.Interval)
for {
select {
case msg := <-c.msgs:
c.verbose("buffer (%d/%d) %v", len(msgs), c.Size, msg)
msgs = append(msgs, msg)
if len(msgs) == c.Size {
c.verbose("exceeded %d messages flushing", c.Size)
c.sendAsync(msgs)
msgs = make([]interface{}, 0, c.Size)
}
case <-tick.C:
if len(msgs) > 0 {
c.verbose("interval reached - flushing %d", len(msgs))
c.sendAsync(msgs)
msgs = make([]interface{}, 0, c.Size)
} else {
c.verbose("interval reached nothing to send")
}
case <-c.quit:
tick.Stop()
c.verbose("exit requested draining msgs")
// drain the msg channel.
for msg := range c.msgs {
c.verbose("buffer (%d/%d) %v", len(msgs), c.Size, msg)
msgs = append(msgs, msg)
}
c.verbose("exit requested flushing %d", len(msgs))
c.sendAsync(msgs)
c.wg.Wait()
c.verbose("exit")
c.shutdown <- struct{}{}
return
}
}
}
// Verbose log.
func (c *Client) verbose(msg string, args ...interface{}) {
if c.Verbose {
c.Logger.Printf(msg, args...)
}
}
// Unconditional log.
func (c *Client) logf(msg string, args ...interface{}) {
c.Logger.Printf(msg, args...)
}
// Set message timestamp if one is not already set.
func (m *Message) setTimestamp(s string) {
if m.Timestamp == "" {
m.Timestamp = s
}
}
// Set message id.
func (m *Message) setMessageId(s string) {
if m.MessageId == "" {
m.MessageId = s
}
}
// Return formatted timestamp.
func timestamp(t time.Time) string {
return strftime.Format("%Y-%m-%dT%H:%M:%S%z", t)
}
// Return uuid string.
func uid() string {
return uuid.NewRandom().String()
}

View File

@@ -0,0 +1,478 @@
package analytics
import "net/http/httptest"
import "encoding/json"
import "net/http"
import "testing"
import "bytes"
import "time"
import "fmt"
import "io"
func mockId() string { return "I'm unique" }
func mockTime() time.Time {
// time.Unix(0, 0) fails on Circle
return time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
}
func mockServer() (chan []byte, *httptest.Server) {
done := make(chan []byte, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
buf := bytes.NewBuffer(nil)
io.Copy(buf, r.Body)
var v interface{}
err := json.Unmarshal(buf.Bytes(), &v)
if err != nil {
panic(err)
}
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
panic(err)
}
done <- b
}))
return done, server
}
func ExampleTrack() {
body, server := mockServer()
defer server.Close()
client := New("h97jamjwbh")
client.Endpoint = server.URL
client.now = mockTime
client.uid = mockId
client.Size = 1
client.Track(&Track{
Event: "Download",
UserId: "123456",
Properties: map[string]interface{}{
"application": "Segment Desktop",
"version": "1.1.0",
"platform": "osx",
},
})
fmt.Printf("%s\n", <-body)
// Output:
// {
// "batch": [
// {
// "event": "Download",
// "messageId": "I'm unique",
// "properties": {
// "application": "Segment Desktop",
// "platform": "osx",
// "version": "1.1.0"
// },
// "timestamp": "2009-11-10T23:00:00+0000",
// "type": "track",
// "userId": "123456"
// }
// ],
// "context": {
// "library": {
// "name": "analytics-go",
// "version": "2.1.0"
// }
// },
// "messageId": "I'm unique",
// "sentAt": "2009-11-10T23:00:00+0000"
// }
}
func ExampleClose() {
body, server := mockServer()
defer server.Close()
client := New("h97jamjwbh")
client.Endpoint = server.URL
client.now = mockTime
client.uid = mockId
client.Track(&Track{
Event: "Download",
UserId: "123456",
Properties: map[string]interface{}{
"application": "Segment Desktop",
"version": "1.1.0",
"platform": "osx",
},
})
client.Close()
fmt.Printf("%s\n", <-body)
// Output:
// {
// "batch": [
// {
// "event": "Download",
// "messageId": "I'm unique",
// "properties": {
// "application": "Segment Desktop",
// "platform": "osx",
// "version": "1.1.0"
// },
// "timestamp": "2009-11-10T23:00:00+0000",
// "type": "track",
// "userId": "123456"
// }
// ],
// "context": {
// "library": {
// "name": "analytics-go",
// "version": "2.1.0"
// }
// },
// "messageId": "I'm unique",
// "sentAt": "2009-11-10T23:00:00+0000"
// }
}
func ExampleInterval() {
body, server := mockServer()
defer server.Close()
client := New("h97jamjwbh")
client.Endpoint = server.URL
client.now = mockTime
client.uid = mockId
client.Track(&Track{
Event: "Download",
UserId: "123456",
Properties: map[string]interface{}{
"application": "Segment Desktop",
"version": "1.1.0",
"platform": "osx",
},
})
// Will flush in 5 seconds (default interval).
fmt.Printf("%s\n", <-body)
// Output:
// {
// "batch": [
// {
// "event": "Download",
// "messageId": "I'm unique",
// "properties": {
// "application": "Segment Desktop",
// "platform": "osx",
// "version": "1.1.0"
// },
// "timestamp": "2009-11-10T23:00:00+0000",
// "type": "track",
// "userId": "123456"
// }
// ],
// "context": {
// "library": {
// "name": "analytics-go",
// "version": "2.1.0"
// }
// },
// "messageId": "I'm unique",
// "sentAt": "2009-11-10T23:00:00+0000"
// }
}
func ExampleTrackWithTimestampSet() {
body, server := mockServer()
defer server.Close()
client := New("h97jamjwbh")
client.Endpoint = server.URL
client.now = mockTime
client.uid = mockId
client.Size = 1
client.Track(&Track{
Event: "Download",
UserId: "123456",
Properties: map[string]interface{}{
"application": "Segment Desktop",
"version": "1.1.0",
"platform": "osx",
},
Message: Message{
Timestamp: timestamp(time.Date(2015, time.July, 10, 23, 0, 0, 0, time.UTC)),
},
})
fmt.Printf("%s\n", <-body)
// Output:
// {
// "batch": [
// {
// "event": "Download",
// "messageId": "I'm unique",
// "properties": {
// "application": "Segment Desktop",
// "platform": "osx",
// "version": "1.1.0"
// },
// "timestamp": "2015-07-10T23:00:00+0000",
// "type": "track",
// "userId": "123456"
// }
// ],
// "context": {
// "library": {
// "name": "analytics-go",
// "version": "2.1.0"
// }
// },
// "messageId": "I'm unique",
// "sentAt": "2009-11-10T23:00:00+0000"
// }
}
func ExampleTrackWithMessageIdSet() {
body, server := mockServer()
defer server.Close()
client := New("h97jamjwbh")
client.Endpoint = server.URL
client.now = mockTime
client.uid = mockId
client.Size = 1
client.Track(&Track{
Event: "Download",
UserId: "123456",
Properties: map[string]interface{}{
"application": "Segment Desktop",
"version": "1.1.0",
"platform": "osx",
},
Message: Message{
MessageId: "abc",
},
})
fmt.Printf("%s\n", <-body)
// Output:
// {
// "batch": [
// {
// "event": "Download",
// "messageId": "abc",
// "properties": {
// "application": "Segment Desktop",
// "platform": "osx",
// "version": "1.1.0"
// },
// "timestamp": "2009-11-10T23:00:00+0000",
// "type": "track",
// "userId": "123456"
// }
// ],
// "context": {
// "library": {
// "name": "analytics-go",
// "version": "2.1.0"
// }
// },
// "messageId": "I'm unique",
// "sentAt": "2009-11-10T23:00:00+0000"
// }
}
func ExampleTrack_context() {
body, server := mockServer()
defer server.Close()
client := New("h97jamjwbh")
client.Endpoint = server.URL
client.now = mockTime
client.uid = mockId
client.Size = 1
client.Track(&Track{
Event: "Download",
UserId: "123456",
Properties: map[string]interface{}{
"application": "Segment Desktop",
"version": "1.1.0",
"platform": "osx",
},
Context: map[string]interface{}{
"whatever": "here",
},
})
fmt.Printf("%s\n", <-body)
// Output:
// {
// "batch": [
// {
// "context": {
// "whatever": "here"
// },
// "event": "Download",
// "messageId": "I'm unique",
// "properties": {
// "application": "Segment Desktop",
// "platform": "osx",
// "version": "1.1.0"
// },
// "timestamp": "2009-11-10T23:00:00+0000",
// "type": "track",
// "userId": "123456"
// }
// ],
// "context": {
// "library": {
// "name": "analytics-go",
// "version": "2.1.0"
// }
// },
// "messageId": "I'm unique",
// "sentAt": "2009-11-10T23:00:00+0000"
// }
}
func ExampleTrack_many() {
body, server := mockServer()
defer server.Close()
client := New("h97jamjwbh")
client.Endpoint = server.URL
client.now = mockTime
client.uid = mockId
client.Size = 3
for i := 0; i < 5; i++ {
client.Track(&Track{
Event: "Download",
UserId: "123456",
Properties: map[string]interface{}{
"application": "Segment Desktop",
"version": i,
},
})
}
fmt.Printf("%s\n", <-body)
// Output:
// {
// "batch": [
// {
// "event": "Download",
// "messageId": "I'm unique",
// "properties": {
// "application": "Segment Desktop",
// "version": 0
// },
// "timestamp": "2009-11-10T23:00:00+0000",
// "type": "track",
// "userId": "123456"
// },
// {
// "event": "Download",
// "messageId": "I'm unique",
// "properties": {
// "application": "Segment Desktop",
// "version": 1
// },
// "timestamp": "2009-11-10T23:00:00+0000",
// "type": "track",
// "userId": "123456"
// },
// {
// "event": "Download",
// "messageId": "I'm unique",
// "properties": {
// "application": "Segment Desktop",
// "version": 2
// },
// "timestamp": "2009-11-10T23:00:00+0000",
// "type": "track",
// "userId": "123456"
// }
// ],
// "context": {
// "library": {
// "name": "analytics-go",
// "version": "2.1.0"
// }
// },
// "messageId": "I'm unique",
// "sentAt": "2009-11-10T23:00:00+0000"
// }
}
func ExampleTrackWithIntegrations() {
body, server := mockServer()
defer server.Close()
client := New("h97jamjwbh")
client.Endpoint = server.URL
client.now = mockTime
client.uid = mockId
client.Size = 1
client.Track(&Track{
Event: "Download",
UserId: "123456",
Properties: map[string]interface{}{
"application": "Segment Desktop",
"version": "1.1.0",
"platform": "osx",
},
Integrations: map[string]interface{}{
"All": true,
"Intercom": false,
"Mixpanel": true,
},
})
fmt.Printf("%s\n", <-body)
// Output:
// {
// "batch": [
// {
// "event": "Download",
// "integrations": {
// "All": true,
// "Intercom": false,
// "Mixpanel": true
// },
// "messageId": "I'm unique",
// "properties": {
// "application": "Segment Desktop",
// "platform": "osx",
// "version": "1.1.0"
// },
// "timestamp": "2009-11-10T23:00:00+0000",
// "type": "track",
// "userId": "123456"
// }
// ],
// "context": {
// "library": {
// "name": "analytics-go",
// "version": "2.1.0"
// }
// },
// "messageId": "I'm unique",
// "sentAt": "2009-11-10T23:00:00+0000"
// }
}
// Tests that calling Close right after creating the client object doesn't
// block.
// Bug: https://github.com/segmentio/analytics-go/issues/43
func TestCloseFinish(_ *testing.T) {
c := New("test")
c.Close()
}

18
vendor/github.com/segmentio/analytics-go/circle.yml generated vendored Normal file
View File

@@ -0,0 +1,18 @@
machine:
environment:
IMPORT_PATH: "github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
dependencies:
pre:
- go get github.com/tools/godep
override:
- mkdir -p "$GOPATH/src/$IMPORT_PATH"
- rsync -azC --delete ./ "$GOPATH/src/$IMPORT_PATH/"
test:
pre:
- make vet
override:
- make test

174
vendor/github.com/segmentio/analytics-go/cli/cli.go generated vendored Normal file
View File

@@ -0,0 +1,174 @@
package main
import (
"encoding/json"
"log"
"os"
"reflect"
"github.com/segmentio/analytics-go"
"github.com/tj/docopt"
)
const Usage = `
Analytics Go CLI
Usage:
analytics track <event> [--properties=<properties>] [--context=<context>] [--writeKey=<writeKey>] [--userId=<userId>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
analytics screen <name> [--properties=<properties>] [--context=<context>] [--writeKey=<writeKey>] [--userId=<userId>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
analytics page <name> [--properties=<properties>] [--context=<context>] [--writeKey=<writeKey>] [--userId=<userId>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
analytics identify [--traits=<traits>] [--context=<context>] [--writeKey=<writeKey>] [--userId=<userId>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
analytics group --groupId=<groupId> [--traits=<traits>] [--properties=<properties>] [--context=<context>] [--writeKey=<writeKey>] [--userId=<userId>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
analytics alias --userId=<userId> --previousId=<previousId> [--traits=<traits>] [--properties=<properties>] [--context=<context>] [--writeKey=<writeKey>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
analytics -h | --help
analytics --version
Options:
-h --help Show this screen.
--version Show version.
`
func main() {
arguments, err := docopt.Parse(Usage, nil, true, "Anaytics Go CLI", false)
check(err)
writeKey := getOptionalString(arguments, "--writeKey")
if writeKey == "" {
writeKey = os.Getenv("SEGMENT_WRITE_KEY")
if writeKey == "" {
log.Fatal("either $SEGMENT_WRITE_KEY or --writeKey must be provided")
}
}
client := analytics.New(writeKey)
client.Size = 1
client.Verbose = true
if arguments["track"].(bool) {
m := &analytics.Track{
Event: arguments["<event>"].(string),
}
properties := getOptionalString(arguments, "--properties")
if properties != "" {
var parsedProperties map[string]interface{}
err := json.Unmarshal([]byte(properties), &parsedProperties)
check(err)
m.Properties = parsedProperties
}
setCommonFields(m, arguments)
check(client.Track(m))
}
if arguments["screen"].(bool) || arguments["page"].(bool) {
m := &analytics.Page{
Name: arguments["<name>"].(string),
}
/* Bug in Go library - page has traits not properties.
properties := getOptionalString(arguments, "--properties")
if properties != "" {
var parsedProperties map[string]interface{}
err := json.Unmarshal([]byte(properties), &parsedProperties)
check(err)
t.Properties = parsedProperties
}
*/
setCommonFields(m, arguments)
check(client.Page(m))
}
if arguments["identify"].(bool) {
m := &analytics.Identify{}
traits := getOptionalString(arguments, "--traits")
if traits != "" {
var parsedTraits map[string]interface{}
err := json.Unmarshal([]byte(traits), &parsedTraits)
check(err)
m.Traits = parsedTraits
}
setCommonFields(m, arguments)
check(client.Identify(m))
}
if arguments["group"].(bool) {
m := &analytics.Group{
GroupId: arguments["--groupId"].(string),
}
traits := getOptionalString(arguments, "--traits")
if traits != "" {
var parsedTraits map[string]interface{}
err := json.Unmarshal([]byte(traits), &parsedTraits)
check(err)
m.Traits = parsedTraits
}
setCommonFields(m, arguments)
check(client.Group(m))
}
if arguments["alias"].(bool) {
m := &analytics.Alias{
PreviousId: arguments["--previousId"].(string),
}
setCommonFields(m, arguments)
check(client.Alias(m))
}
client.Close()
}
func setCommonFields(message interface{}, arguments map[string]interface{}) {
userId := getOptionalString(arguments, "--userId")
if userId != "" {
setFieldValue(message, "UserId", userId)
}
anonymousId := getOptionalString(arguments, "--anonymousId")
if anonymousId != "" {
setFieldValue(message, "AnonymousId", anonymousId)
}
integrations := getOptionalString(arguments, "--integrations")
if integrations != "" {
var parsedIntegrations map[string]interface{}
err := json.Unmarshal([]byte(integrations), &parsedIntegrations)
check(err)
setFieldValue(message, "Integrations", parsedIntegrations)
}
context := getOptionalString(arguments, "--context")
if context != "" {
var parsedContext map[string]interface{}
err := json.Unmarshal([]byte(context), &parsedContext)
check(err)
setFieldValue(message, "Context", parsedContext)
}
timestamp := getOptionalString(arguments, "--timestamp")
if timestamp != "" {
setFieldValue(message, "Timestamp", timestamp)
}
}
func setFieldValue(target interface{}, field string, value interface{}) {
reflect.ValueOf(target).Elem().FieldByName(field).Set(reflect.ValueOf(value))
}
func getOptionalString(m map[string]interface{}, k string) string {
v := m[k]
if v == nil {
return ""
}
return v.(string)
}
func check(err error) {
if err != nil {
log.Fatal(err)
}
}

View File

@@ -0,0 +1,36 @@
package main
import "github.com/segmentio/analytics-go"
import "time"
func main() {
client := analytics.New("h97jamjwbh")
client.Interval = 30 * time.Second
client.Size = 100
client.Verbose = true
done := time.After(3 * time.Second)
tick := time.Tick(50 * time.Millisecond)
out:
for {
select {
case <-done:
println("exiting")
break out
case <-tick:
client.Track(&analytics.Track{
Event: "Download",
UserId: "123456",
Properties: map[string]interface{}{
"application": "Segment Desktop",
"version": "1.1.0",
"platform": "osx",
},
})
}
}
println("flushing")
client.Close()
}

80
vendor/github.com/segmentio/backo-go/README.md generated vendored Normal file
View File

@@ -0,0 +1,80 @@
Backo [![GoDoc](http://godoc.org/github.com/segmentio/backo-go?status.png)](http://godoc.org/github.com/segmentio/backo-go)
-----
Exponential backoff for Go (Go port of segmentio/backo).
Usage
-----
```go
import "github.com/segmentio/backo-go"
// Create a Backo instance.
backo := backo.NewBacko(milliseconds(100), 2, 1, milliseconds(10*1000))
// OR with defaults.
backo := backo.DefaultBacko()
// Use the ticker API.
ticker := b.NewTicker()
for {
timeout := time.After(5 * time.Minute)
select {
case <-ticker.C:
fmt.Println("ticked")
case <- timeout:
fmt.Println("timed out")
}
}
// Or simply work with backoff intervals directly.
for i := 0; i < n; i++ {
// Sleep the current goroutine.
backo.Sleep(i)
// Retrieve the duration manually.
duration := backo.Duration(i)
}
```
License
-------
```
WWWWWW||WWWWWW
W W W||W W W
||
( OO )__________
/ | \
/o o| MIT \
\___/||_||__||_|| *
|| || || ||
_||_|| _||_||
(__|__|(__|__|
The MIT License (MIT)
Copyright (c) 2015 Segment, Inc.
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.
```
[1]: http://github.com/segmentio/backo-java
[2]: http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.segment.backo&a=backo&v=LATEST

83
vendor/github.com/segmentio/backo-go/backo.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
package backo
import (
"math"
"math/rand"
"time"
)
type Backo struct {
base time.Duration
factor uint8
jitter float64
cap time.Duration
}
// Creates a backo instance with the given parameters
func NewBacko(base time.Duration, factor uint8, jitter float64, cap time.Duration) *Backo {
return &Backo{base, factor, jitter, cap}
}
// Creates a backo instance with the following defaults:
// base: 100 milliseconds
// factor: 2
// jitter: 0
// cap: 10 seconds
func DefaultBacko() *Backo {
return NewBacko(time.Millisecond*100, 2, 0, time.Second*10)
}
// Duration returns the backoff interval for the given attempt.
func (backo *Backo) Duration(attempt int) time.Duration {
duration := float64(backo.base) * math.Pow(float64(backo.factor), float64(attempt))
if backo.jitter != 0 {
random := rand.Float64()
deviation := math.Floor(random * backo.jitter * duration)
if (int(math.Floor(random*10)) & 1) == 0 {
duration = duration - deviation
} else {
duration = duration + deviation
}
}
duration = math.Min(float64(duration), float64(backo.cap))
return time.Duration(duration)
}
// Sleep pauses the current goroutine for the backoff interval for the given attempt.
func (backo *Backo) Sleep(attempt int) {
duration := backo.Duration(attempt)
time.Sleep(duration)
}
type Ticker struct {
done chan struct{}
C <-chan time.Time
}
func (b *Backo) NewTicker() *Ticker {
c := make(chan time.Time, 1)
ticker := &Ticker{
done: make(chan struct{}, 1),
C: c,
}
go func() {
for i := 0; ; i++ {
select {
case t := <-time.After(b.Duration(i)):
c <- t
case <-ticker.done:
close(c)
return
}
}
}()
return ticker
}
func (t *Ticker) Stop() {
t.done <- struct{}{}
}

77
vendor/github.com/segmentio/backo-go/backo_test.go generated vendored Normal file
View File

@@ -0,0 +1,77 @@
package backo
import (
"fmt"
"math"
"testing"
"time"
"github.com/bmizerany/assert"
)
// Tests default backo behaviour.
func TestDefaults(t *testing.T) {
backo := DefaultBacko()
assert.Equal(t, milliseconds(100), backo.Duration(0))
assert.Equal(t, milliseconds(200), backo.Duration(1))
assert.Equal(t, milliseconds(400), backo.Duration(2))
assert.Equal(t, milliseconds(800), backo.Duration(3))
}
// Tests backo does not exceed cap.
func TestCap(t *testing.T) {
backo := NewBacko(milliseconds(100), 2, 0, milliseconds(600))
assert.Equal(t, milliseconds(100), backo.Duration(0))
assert.Equal(t, milliseconds(200), backo.Duration(1))
assert.Equal(t, milliseconds(400), backo.Duration(2))
assert.Equal(t, milliseconds(600), backo.Duration(3))
}
// Tests that jitter adds randomness.
func TestJitter(t *testing.T) {
defaultBacko := NewBacko(milliseconds(100), 2, 1, milliseconds(10*1000))
jitterBacko := NewBacko(milliseconds(100), 2, 1, milliseconds(10*1000))
// TODO: Check jittered durations are within a range.
assert.NotEqual(t, jitterBacko.Duration(0), defaultBacko.Duration(0))
assert.NotEqual(t, jitterBacko.Duration(1), defaultBacko.Duration(1))
assert.NotEqual(t, jitterBacko.Duration(2), defaultBacko.Duration(2))
assert.NotEqual(t, jitterBacko.Duration(3), defaultBacko.Duration(3))
}
func ExampleBacko_BackoffDefault() {
b := DefaultBacko()
ticker := b.NewTicker()
for i := 0; i < 6; i++ {
start := time.Now()
select {
case t := <-ticker.C:
fmt.Println(nearest10Millis(t.Sub(start)))
}
}
ticker.Stop()
// Output:
// 100
// 200
// 400
// 800
// 1600
// 3200
}
func nearest10Millis(d time.Duration) float64 {
// Typically d is something like 11 or 21, so do some magic to round the
// durations to the nearest 10. We divide d by 10, floor it, and multiply it
// by 10 again.
return math.Floor(float64(d/time.Millisecond/10) * 10)
}
// Returns the given milliseconds as time.Duration
func milliseconds(ms int64) time.Duration {
return time.Duration(ms * 1000 * 1000)
}

5
vendor/github.com/xtgo/uuid/AUTHORS generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# This source file refers to The gocql Authors for copyright purposes.
Christoph Hack <christoph@tux21b.org>
Jonathan Rudenberg <jonathan@titanous.com>
Thorsten von Eicken <tve@rightscale.com>

27
vendor/github.com/xtgo/uuid/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2012 The gocql Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

204
vendor/github.com/xtgo/uuid/uuid.go generated vendored Normal file
View File

@@ -0,0 +1,204 @@
// Copyright (c) 2012 The gocql Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package uuid can be used to generate and parse universally unique
// identifiers, a standardized format in the form of a 128 bit number.
//
// http://tools.ietf.org/html/rfc4122
package uuid
import (
"crypto/rand"
"encoding/hex"
"errors"
"io"
"net"
"strconv"
"time"
)
type UUID [16]byte
var hardwareAddr []byte
const (
VariantNCSCompat = 0
VariantIETF = 2
VariantMicrosoft = 6
VariantFuture = 7
)
func init() {
if interfaces, err := net.Interfaces(); err == nil {
for _, i := range interfaces {
if i.Flags&net.FlagLoopback == 0 && len(i.HardwareAddr) > 0 {
hardwareAddr = i.HardwareAddr
break
}
}
}
if hardwareAddr == nil {
// If we failed to obtain the MAC address of the current computer,
// we will use a randomly generated 6 byte sequence instead and set
// the multicast bit as recommended in RFC 4122.
hardwareAddr = make([]byte, 6)
_, err := io.ReadFull(rand.Reader, hardwareAddr)
if err != nil {
panic(err)
}
hardwareAddr[0] = hardwareAddr[0] | 0x01
}
}
// Parse parses a 32 digit hexadecimal number (that might contain hyphens)
// representing an UUID.
func Parse(input string) (UUID, error) {
var u UUID
j := 0
for i := 0; i < len(input); i++ {
b := input[i]
switch {
default:
fallthrough
case j == 32:
goto err
case b == '-':
continue
case '0' <= b && b <= '9':
b -= '0'
case 'a' <= b && b <= 'f':
b -= 'a' - 10
case 'A' <= b && b <= 'F':
b -= 'A' - 10
}
u[j/2] |= b << byte(^j&1<<2)
j++
}
if j == 32 {
return u, nil
}
err:
return UUID{}, errors.New("invalid UUID " + strconv.Quote(input))
}
// FromBytes converts a raw byte slice to an UUID. It will panic if the slice
// isn't exactly 16 bytes long.
func FromBytes(input []byte) UUID {
var u UUID
if len(input) != 16 {
panic("UUIDs must be exactly 16 bytes long")
}
copy(u[:], input)
return u
}
// NewRandom generates a totally random UUID (version 4) as described in
// RFC 4122.
func NewRandom() UUID {
var u UUID
io.ReadFull(rand.Reader, u[:])
u[6] &= 0x0F // clear version
u[6] |= 0x40 // set version to 4 (random uuid)
u[8] &= 0x3F // clear variant
u[8] |= 0x80 // set to IETF variant
return u
}
var timeBase = time.Date(1582, time.October, 15, 0, 0, 0, 0, time.UTC).Unix()
// NewTime generates a new time based UUID (version 1) as described in RFC
// 4122. This UUID contains the MAC address of the node that generated the
// UUID, a timestamp and a sequence number.
func NewTime() UUID {
var u UUID
now := time.Now().In(time.UTC)
t := uint64(now.Unix()-timeBase)*10000000 + uint64(now.Nanosecond()/100)
u[0], u[1], u[2], u[3] = byte(t>>24), byte(t>>16), byte(t>>8), byte(t)
u[4], u[5] = byte(t>>40), byte(t>>32)
u[6], u[7] = byte(t>>56)&0x0F, byte(t>>48)
var clockSeq [2]byte
io.ReadFull(rand.Reader, clockSeq[:])
u[8] = clockSeq[1]
u[9] = clockSeq[0]
copy(u[10:], hardwareAddr)
u[6] |= 0x10 // set version to 1 (time based uuid)
u[8] &= 0x3F // clear variant
u[8] |= 0x80 // set to IETF variant
return u
}
// String returns the UUID in it's canonical form, a 32 digit hexadecimal
// number in the form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
func (u UUID) String() string {
buf := [36]byte{8: '-', 13: '-', 18: '-', 23: '-'}
hex.Encode(buf[0:], u[0:4])
hex.Encode(buf[9:], u[4:6])
hex.Encode(buf[14:], u[6:8])
hex.Encode(buf[19:], u[8:10])
hex.Encode(buf[24:], u[10:])
return string(buf[:])
}
// Bytes returns the raw byte slice for this UUID. A UUID is always 128 bits
// (16 bytes) long.
func (u UUID) Bytes() []byte {
return u[:]
}
// Variant returns the variant of this UUID. This package will only generate
// UUIDs in the IETF variant.
func (u UUID) Variant() int {
x := u[8]
switch byte(0) {
case x & 0x80:
return VariantNCSCompat
case x & 0x40:
return VariantIETF
case x & 0x20:
return VariantMicrosoft
}
return VariantFuture
}
// Version extracts the version of this UUID variant. The RFC 4122 describes
// five kinds of UUIDs.
func (u UUID) Version() int {
return int(u[6] & 0xF0 >> 4)
}
// Node extracts the MAC address of the node who generated this UUID. It will
// return nil if the UUID is not a time based UUID (version 1).
func (u UUID) Node() []byte {
if u.Version() != 1 {
return nil
}
return u[10:]
}
// Timestamp extracts the timestamp information from a time based UUID
// (version 1).
func (u UUID) Timestamp() uint64 {
if u.Version() != 1 {
return 0
}
return uint64(u[0])<<24 + uint64(u[1])<<16 + uint64(u[2])<<8 +
uint64(u[3]) + uint64(u[4])<<40 + uint64(u[5])<<32 +
uint64(u[7])<<48 + uint64(u[6]&0x0F)<<56
}
// Time is like Timestamp, except that it returns a time.Time.
func (u UUID) Time() time.Time {
t := u.Timestamp()
if t == 0 {
return time.Time{}
}
sec := t / 10000000
nsec := t - sec
return time.Unix(int64(sec)+timeBase, int64(nsec))
}

102
vendor/github.com/xtgo/uuid/uuid_test.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
// Copyright (c) 2012 The gocql Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"testing"
)
func TestNil(t *testing.T) {
var uuid UUID
want, got := "00000000-0000-0000-0000-000000000000", uuid.String()
if want != got {
t.Fatalf("TestNil: expected %q got %q", want, got)
}
}
var tests = []struct {
input string
variant int
version int
}{
{"b4f00409-cef8-4822-802c-deb20704c365", VariantIETF, 4},
{"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
{"00000000-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
{"3051a8d7-aea7-1801-e0bf-bc539dd60cf3", VariantFuture, 1},
{"3051a8d7-aea7-2801-e0bf-bc539dd60cf3", VariantFuture, 2},
{"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 3},
{"3051a8d7-aea7-4801-e0bf-bc539dd60cf3", VariantFuture, 4},
{"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 5},
{"d0e817e1-e4b1-1801-3fe6-b4b60ccecf9d", VariantNCSCompat, 0},
{"d0e817e1-e4b1-1801-bfe6-b4b60ccecf9d", VariantIETF, 1},
{"d0e817e1-e4b1-1801-dfe6-b4b60ccecf9d", VariantMicrosoft, 0},
{"d0e817e1-e4b1-1801-ffe6-b4b60ccecf9d", VariantFuture, 0},
}
func TestPredefined(t *testing.T) {
for i := range tests {
uuid, err := Parse(tests[i].input)
if err != nil {
t.Errorf("Parse #%d: %v", i, err)
continue
}
if str := uuid.String(); str != tests[i].input {
t.Errorf("String #%d: expected %q got %q", i, tests[i].input, str)
continue
}
if variant := uuid.Variant(); variant != tests[i].variant {
t.Errorf("Variant #%d: expected %d got %d", i, tests[i].variant, variant)
}
if tests[i].variant == VariantIETF {
if version := uuid.Version(); version != tests[i].version {
t.Errorf("Version #%d: expected %d got %d", i, tests[i].version, version)
}
}
}
}
func TestNewRandom(t *testing.T) {
for i := 0; i < 20; i++ {
uuid := NewRandom()
if variant := uuid.Variant(); variant != VariantIETF {
t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant)
}
if version := uuid.Version(); version != 4 {
t.Errorf("wrong version. expected %d got %d", 4, version)
}
}
}
func TestNewTime(t *testing.T) {
var node []byte
timestamp := uint64(0)
for i := 0; i < 20; i++ {
uuid := NewTime()
if variant := uuid.Variant(); variant != VariantIETF {
t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant)
}
if version := uuid.Version(); version != 1 {
t.Errorf("wrong version. expected %d got %d", 1, version)
}
if n := uuid.Node(); !bytes.Equal(n, node) && i > 0 {
t.Errorf("wrong node. expected %x, got %x", node, n)
} else if i == 0 {
node = n
}
ts := uuid.Timestamp()
if ts < timestamp {
t.Errorf("timestamps must grow")
}
timestamp = ts
}
}