not tracked no coverage low coverage * * * * * * * * high coverage

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Account API support - Fetch and Update
    // See: https://login.circonus.com/resources/api/calls/account
    // Note: Create and Delete are not supported for Accounts via the API

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // AccountLimit defines a usage limit imposed on account
    type AccountLimit struct {
            Limit uint   `json:"_limit,omitempty"` // uint >=0
            Type  string `json:"_type,omitempty"`  // string
            Used  uint   `json:"_used,omitempty"`  // uint >=0
    }

    // AccountInvite defines outstanding invites
    type AccountInvite struct {
            Email string `json:"email"` // string
            Role  string `json:"role"`  // string
    }

    // AccountUser defines current users
    type AccountUser struct {
            Role    string `json:"role"` // string
            UserCID string `json:"user"` // string
    }

    // Account defines an account. See https://login.circonus.com/resources/api/calls/account for more information.
    type Account struct {
            Address1      *string         `json:"address1,omitempty"`        // string or null
            Address2      *string         `json:"address2,omitempty"`        // string or null
            CCEmail       *string         `json:"cc_email,omitempty"`        // string or null
            CID           string          `json:"_cid,omitempty"`            // string
            City          *string         `json:"city,omitempty"`            // string or null
            ContactGroups []string        `json:"_contact_groups,omitempty"` // [] len >= 0
            Country       string          `json:"country_code,omitempty"`    // string
            Description   *string         `json:"description,omitempty"`     // string or null
            Invites       []AccountInvite `json:"invites,omitempty"`         // [] len >= 0
            Name          string          `json:"name,omitempty"`            // string
            OwnerCID      string          `json:"_owner,omitempty"`          // string
            StateProv     *string         `json:"state_prov,omitempty"`      // string or null
            Timezone      string          `json:"timezone,omitempty"`        // string
            UIBaseURL     string          `json:"_ui_base_url,omitempty"`    // string
            Usage         []AccountLimit  `json:"_usage,omitempty"`          // [] len >= 0
            Users         []AccountUser   `json:"users,omitempty"`           // [] len >= 0
    }

    // FetchAccount retrieves account with passed cid. Pass nil for '/account/current'.
    func (a *API) FetchAccount(cid CIDType) (*Account, error) {
            var accountCID string

            if cid == nil || *cid == "" {
                    accountCID = config.AccountPrefix + "/current"
            } else {
                    accountCID = string(*cid)
            }

            matched, err := regexp.MatchString(config.AccountCIDRegex, accountCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid account CID [%s]", accountCID)
            }

            result, err := a.Get(accountCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] account fetch, received JSON: %s", string(result))
            }

            account := new(Account)
            if err := json.Unmarshal(result, account); err != nil {
                    return nil, err
            }

            return account, nil
    }

    // FetchAccounts retrieves all accounts available to the API Token.
    func (a *API) FetchAccounts() (*[]Account, error) {
            result, err := a.Get(config.AccountPrefix)
            if err != nil {
                    return nil, err
            }

            var accounts []Account
            if err := json.Unmarshal(result, &accounts); err != nil {
                    return nil, err
            }

            return &accounts, nil
    }

    // UpdateAccount updates passed account.
    func (a *API) UpdateAccount(cfg *Account) (*Account, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid account config [nil]")
            }

            accountCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.AccountCIDRegex, accountCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid account CID [%s]", accountCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] account update, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(accountCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            account := &Account{}
            if err := json.Unmarshal(result, account); err != nil {
                    return nil, err
            }

            return account, nil
    }

    // SearchAccounts returns accounts matching a filter (search queries are not
    // suppoted by the account endpoint). Pass nil as filter for all accounts the
    // API Token can access.
    func (a *API) SearchAccounts(filterCriteria *SearchFilterType) (*[]Account, error) {
            q := url.Values{}

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchAccounts()
            }

            reqURL := url.URL{
                    Path:     config.AccountPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var accounts []Account
            if err := json.Unmarshal(result, &accounts); err != nil {
                    return nil, err
            }

            return &accounts, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Acknowledgement API support - Fetch, Create, Update, Delete*, and Search
    // See: https://login.circonus.com/resources/api/calls/acknowledgement
    // *  : delete (cancel) by updating with AcknowledgedUntil set to 0

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // Acknowledgement defines a acknowledgement. See https://login.circonus.com/resources/api/calls/acknowledgement for more information.
    type Acknowledgement struct {
            AcknowledgedBy    string      `json:"_acknowledged_by,omitempty"`   // string
            AcknowledgedOn    uint        `json:"_acknowledged_on,omitempty"`   // uint
            AcknowledgedUntil interface{} `json:"acknowledged_until,omitempty"` // NOTE received as uint; can be set using string or uint
            Active            bool        `json:"_active,omitempty"`            // bool
            AlertCID          string      `json:"alert,omitempty"`              // string
            CID               string      `json:"_cid,omitempty"`               // string
            LastModified      uint        `json:"_last_modified,omitempty"`     // uint
            LastModifiedBy    string      `json:"_last_modified_by,omitempty"`  // string
            Notes             string      `json:"notes,omitempty"`              // string
    }

    // NewAcknowledgement returns new Acknowledgement (with defaults, if applicable).
    func NewAcknowledgement() *Acknowledgement {
            return &Acknowledgement{}
    }

    // FetchAcknowledgement retrieves acknowledgement with passed cid.
    func (a *API) FetchAcknowledgement(cid CIDType) (*Acknowledgement, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid acknowledgement CID [none]")
            }

            acknowledgementCID := string(*cid)

            matched, err := regexp.MatchString(config.AcknowledgementCIDRegex, acknowledgementCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid acknowledgement CID [%s]", acknowledgementCID)
            }

            result, err := a.Get(acknowledgementCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] acknowledgement fetch, received JSON: %s", string(result))
            }

            acknowledgement := &Acknowledgement{}
            if err := json.Unmarshal(result, acknowledgement); err != nil {
                    return nil, err
            }

            return acknowledgement, nil
    }

    // FetchAcknowledgements retrieves all acknowledgements available to the API Token.
    func (a *API) FetchAcknowledgements() (*[]Acknowledgement, error) {
            result, err := a.Get(config.AcknowledgementPrefix)
            if err != nil {
                    return nil, err
            }

            var acknowledgements []Acknowledgement
            if err := json.Unmarshal(result, &acknowledgements); err != nil {
                    return nil, err
            }

            return &acknowledgements, nil
    }

    // UpdateAcknowledgement updates passed acknowledgement.
    func (a *API) UpdateAcknowledgement(cfg *Acknowledgement) (*Acknowledgement, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid acknowledgement config [nil]")
            }

            acknowledgementCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.AcknowledgementCIDRegex, acknowledgementCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid acknowledgement CID [%s]", acknowledgementCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] acknowledgement update, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(acknowledgementCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            acknowledgement := &Acknowledgement{}
            if err := json.Unmarshal(result, acknowledgement); err != nil {
                    return nil, err
            }

            return acknowledgement, nil
    }

    // CreateAcknowledgement creates a new acknowledgement.
    func (a *API) CreateAcknowledgement(cfg *Acknowledgement) (*Acknowledgement, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid acknowledgement config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            result, err := a.Post(config.AcknowledgementPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] acknowledgement create, sending JSON: %s", string(jsonCfg))
            }

            acknowledgement := &Acknowledgement{}
            if err := json.Unmarshal(result, acknowledgement); err != nil {
                    return nil, err
            }

            return acknowledgement, nil
    }

    // SearchAcknowledgements returns acknowledgements matching
    // the specified search query and/or filter. If nil is passed for
    // both parameters all acknowledgements will be returned.
    func (a *API) SearchAcknowledgements(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Acknowledgement, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchAcknowledgements()
            }

            reqURL := url.URL{
                    Path:     config.AcknowledgementPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var acknowledgements []Acknowledgement
            if err := json.Unmarshal(result, &acknowledgements); err != nil {
                    return nil, err
            }

            return &acknowledgements, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Alert API support - Fetch and Search
    // See: https://login.circonus.com/resources/api/calls/alert

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // Alert defines a alert. See https://login.circonus.com/resources/api/calls/alert for more information.
    type Alert struct {
            AcknowledgementCID *string  `json:"_acknowledgement,omitempty"` // string or null
            AlertURL           string   `json:"_alert_url,omitempty"`       // string
            BrokerCID          string   `json:"_broker,omitempty"`          // string
            CheckCID           string   `json:"_check,omitempty"`           // string
            CheckName          string   `json:"_check_name,omitempty"`      // string
            CID                string   `json:"_cid,omitempty"`             // string
            ClearedOn          *uint    `json:"_cleared_on,omitempty"`      // uint or null
            ClearedValue       *string  `json:"_cleared_value,omitempty"`   // string or null
            Maintenance        []string `json:"_maintenance,omitempty"`     // [] len >= 0
            MetricLinkURL      *string  `json:"_metric_link,omitempty"`     // string or null
            MetricName         string   `json:"_metric_name,omitempty"`     // string
            MetricNotes        *string  `json:"_metric_notes,omitempty"`    // string or null
            OccurredOn         uint     `json:"_occurred_on,omitempty"`     // uint
            RuleSetCID         string   `json:"_rule_set,omitempty"`        // string
            Severity           uint     `json:"_severity,omitempty"`        // uint
            Tags               []string `json:"_tags,omitempty"`            // [] len >= 0
            Value              string   `json:"_value,omitempty"`           // string
    }

    // NewAlert returns a new alert (with defaults, if applicable)
    func NewAlert() *Alert {
            return &Alert{}
    }

    // FetchAlert retrieves alert with passed cid.
    func (a *API) FetchAlert(cid CIDType) (*Alert, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid alert CID [none]")
            }

            alertCID := string(*cid)

            matched, err := regexp.MatchString(config.AlertCIDRegex, alertCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid alert CID [%s]", alertCID)
            }

            result, err := a.Get(alertCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch alert, received JSON: %s", string(result))
            }

            alert := &Alert{}
            if err := json.Unmarshal(result, alert); err != nil {
                    return nil, err
            }

            return alert, nil
    }

    // FetchAlerts retrieves all alerts available to the API Token.
    func (a *API) FetchAlerts() (*[]Alert, error) {
            result, err := a.Get(config.AlertPrefix)
            if err != nil {
                    return nil, err
            }

            var alerts []Alert
            if err := json.Unmarshal(result, &alerts); err != nil {
                    return nil, err
            }

            return &alerts, nil
    }

    // SearchAlerts returns alerts matching the specified search query
    // and/or filter. If nil is passed for both parameters all alerts
    // will be returned.
    func (a *API) SearchAlerts(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Alert, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchAlerts()
            }

            reqURL := url.URL{
                    Path:     config.AlertPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var alerts []Alert
            if err := json.Unmarshal(result, &alerts); err != nil {
                    return nil, err
            }

            return &alerts, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Annotation API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/annotation

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // Annotation defines a annotation. See https://login.circonus.com/resources/api/calls/annotation for more information.
    type Annotation struct {
            Category       string   `json:"category"`                    // string
            CID            string   `json:"_cid,omitempty"`              // string
            Created        uint     `json:"_created,omitempty"`          // uint
            Description    string   `json:"description"`                 // string
            LastModified   uint     `json:"_last_modified,omitempty"`    // uint
            LastModifiedBy string   `json:"_last_modified_by,omitempty"` // string
            RelatedMetrics []string `json:"rel_metrics"`                 // [] len >= 0
            Start          uint     `json:"start"`                       // uint
            Stop           uint     `json:"stop"`                        // uint
            Title          string   `json:"title"`                       // string
    }

    // NewAnnotation returns a new Annotation (with defaults, if applicable)
    func NewAnnotation() *Annotation {
            return &Annotation{}
    }

    // FetchAnnotation retrieves annotation with passed cid.
    func (a *API) FetchAnnotation(cid CIDType) (*Annotation, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid annotation CID [none]")
            }

            annotationCID := string(*cid)

            matched, err := regexp.MatchString(config.AnnotationCIDRegex, annotationCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid annotation CID [%s]", annotationCID)
            }

            result, err := a.Get(annotationCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch annotation, received JSON: %s", string(result))
            }

            annotation := &Annotation{}
            if err := json.Unmarshal(result, annotation); err != nil {
                    return nil, err
            }

            return annotation, nil
    }

    // FetchAnnotations retrieves all annotations available to the API Token.
    func (a *API) FetchAnnotations() (*[]Annotation, error) {
            result, err := a.Get(config.AnnotationPrefix)
            if err != nil {
                    return nil, err
            }

            var annotations []Annotation
            if err := json.Unmarshal(result, &annotations); err != nil {
                    return nil, err
            }

            return &annotations, nil
    }

    // UpdateAnnotation updates passed annotation.
    func (a *API) UpdateAnnotation(cfg *Annotation) (*Annotation, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid annotation config [nil]")
            }

            annotationCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.AnnotationCIDRegex, annotationCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid annotation CID [%s]", annotationCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update annotation, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(annotationCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            annotation := &Annotation{}
            if err := json.Unmarshal(result, annotation); err != nil {
                    return nil, err
            }

            return annotation, nil
    }

    // CreateAnnotation creates a new annotation.
    func (a *API) CreateAnnotation(cfg *Annotation) (*Annotation, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid annotation config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create annotation, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.AnnotationPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            annotation := &Annotation{}
            if err := json.Unmarshal(result, annotation); err != nil {
                    return nil, err
            }

            return annotation, nil
    }

    // DeleteAnnotation deletes passed annotation.
    func (a *API) DeleteAnnotation(cfg *Annotation) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid annotation config [nil]")
            }

            return a.DeleteAnnotationByCID(CIDType(&cfg.CID))
    }

    // DeleteAnnotationByCID deletes annotation with passed cid.
    func (a *API) DeleteAnnotationByCID(cid CIDType) (bool, error) {
            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid annotation CID [none]")
            }

            annotationCID := string(*cid)

            matched, err := regexp.MatchString(config.AnnotationCIDRegex, annotationCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid annotation CID [%s]", annotationCID)
            }

            _, err = a.Delete(annotationCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchAnnotations returns annotations matching the specified
    // search query and/or filter. If nil is passed for both parameters
    // all annotations will be returned.
    func (a *API) SearchAnnotations(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Annotation, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchAnnotations()
            }

            reqURL := url.URL{
                    Path:     config.AnnotationPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var annotations []Annotation
            if err := json.Unmarshal(result, &annotations); err != nil {
                    return nil, err
            }

            return &annotations, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package api

    import (
            "bytes"
            crand "crypto/rand"
            "crypto/tls"
            "crypto/x509"
            "errors"
            "fmt"
            "io/ioutil"
            "log"
            "math"
            "math/big"
            "math/rand"
            "net"
            "net/http"
            "net/url"
            "os"
            "regexp"
            "strings"
            "sync"
            "time"

            "github.com/hashicorp/go-retryablehttp"
    )

    func init() {
            n, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
            if err != nil {
                    rand.Seed(time.Now().UTC().UnixNano())
                    return
            }
            rand.Seed(n.Int64())
    }

    const (
            // a few sensible defaults
            defaultAPIURL = "https://api.circonus.com/v2"
            defaultAPIApp = "circonus-gometrics"
            minRetryWait  = 1 * time.Second
            maxRetryWait  = 15 * time.Second
            maxRetries    = 4 // equating to 1 + maxRetries total attempts
    )

    // TokenKeyType - Circonus API Token key
    type TokenKeyType string

    // TokenAppType - Circonus API Token app name
    type TokenAppType string

    // TokenAccountIDType - Circonus API Token account id
    type TokenAccountIDType string

    // CIDType Circonus object cid
    type CIDType *string

    // IDType Circonus object id
    type IDType int

    // URLType submission url type
    type URLType string

    // SearchQueryType search query (see: https://login.circonus.com/resources/api#searching)
    type SearchQueryType string

    // SearchFilterType search filter (see: https://login.circonus.com/resources/api#filtering)
    type SearchFilterType map[string][]string

    // TagType search/select/custom tag(s) type
    type TagType []string

    // Config options for Circonus API
    type Config struct {
            // URL defines the API URL - default https://api.circonus.com/v2/
            URL string

            // TokenKey defines the key to use when communicating with the API
            TokenKey string

            // TokenApp defines the app to use when communicating with the API
            TokenApp string

            TokenAccountID string

            // CACert deprecating, use TLSConfig instead
            CACert *x509.CertPool

            // TLSConfig defines a custom tls configuration to use when communicating with the API
            TLSConfig *tls.Config

            Log   *log.Logger
            Debug bool
    }

    // API Circonus API
    type API struct {
            apiURL                  *url.URL
            key                     TokenKeyType
            app                     TokenAppType
            accountID               TokenAccountIDType
            caCert                  *x509.CertPool
            tlsConfig               *tls.Config
            Debug                   bool
            Log                     *log.Logger
            useExponentialBackoff   bool
            useExponentialBackoffmu sync.Mutex
    }

    // NewClient returns a new Circonus API (alias for New)
    func NewClient(ac *Config) (*API, error) {
            return New(ac)
    }

    // NewAPI returns a new Circonus API (alias for New)
    func NewAPI(ac *Config) (*API, error) {
            return New(ac)
    }

    // New returns a new Circonus API
    func New(ac *Config) (*API, error) {

            if ac == nil {
                    return nil, errors.New("Invalid API configuration (nil)")
            }

            key := TokenKeyType(ac.TokenKey)
            if key == "" {
                    return nil, errors.New("API Token is required")
            }

            app := TokenAppType(ac.TokenApp)
            if app == "" {
                    app = defaultAPIApp
            }

            acctID := TokenAccountIDType(ac.TokenAccountID)

            au := string(ac.URL)
            if au == "" {
                    au = defaultAPIURL
            }
            if !strings.Contains(au, "/") {
                    // if just a hostname is passed, ASSume "https" and a path prefix of "/v2"
                    au = fmt.Sprintf("https://%s/v2", ac.URL)
            }
            if last := len(au) - 1; last >= 0 && au[last] == '/' {
                    // strip off trailing '/'
                    au = au[:last]
            }
            apiURL, err := url.Parse(au)
            if err != nil {
                    return nil, err
            }

            a := &API{
                    apiURL:    apiURL,
                    key:       key,
                    app:       app,
                    accountID: acctID,
                    caCert:    ac.CACert,
                    tlsConfig: ac.TLSConfig,
                    Debug:     ac.Debug,
                    Log:       ac.Log,
                    useExponentialBackoff: false,
            }

            a.Debug = ac.Debug
            a.Log = ac.Log
            if a.Debug && a.Log == nil {
                    a.Log = log.New(os.Stderr, "", log.LstdFlags)
            }
            if a.Log == nil {
                    a.Log = log.New(ioutil.Discard, "", log.LstdFlags)
            }

            return a, nil
    }

    // EnableExponentialBackoff enables use of exponential backoff for next API call(s)
    // and use exponential backoff for all API calls until exponential backoff is disabled.
    func (a *API) EnableExponentialBackoff() {
            a.useExponentialBackoffmu.Lock()
            a.useExponentialBackoff = true
            a.useExponentialBackoffmu.Unlock()
    }

    // DisableExponentialBackoff disables use of exponential backoff. If a request using
    // exponential backoff is currently running, it will stop using exponential backoff
    // on its next iteration (if needed).
    func (a *API) DisableExponentialBackoff() {
            a.useExponentialBackoffmu.Lock()
            a.useExponentialBackoff = false
            a.useExponentialBackoffmu.Unlock()
    }

    // Get API request
    func (a *API) Get(reqPath string) ([]byte, error) {
            return a.apiRequest("GET", reqPath, nil)
    }

    // Delete API request
    func (a *API) Delete(reqPath string) ([]byte, error) {
            return a.apiRequest("DELETE", reqPath, nil)
    }

    // Post API request
    func (a *API) Post(reqPath string, data []byte) ([]byte, error) {
            return a.apiRequest("POST", reqPath, data)
    }

    // Put API request
    func (a *API) Put(reqPath string, data []byte) ([]byte, error) {
            return a.apiRequest("PUT", reqPath, data)
    }

    func backoff(interval uint) float64 {
            return math.Floor(((float64(interval) * (1 + rand.Float64())) / 2) + .5)
    }

    // apiRequest manages retry strategy for exponential backoffs
    func (a *API) apiRequest(reqMethod string, reqPath string, data []byte) ([]byte, error) {
            backoffs := []uint{2, 4, 8, 16, 32}
            attempts := 0
            success := false

            var result []byte
            var err error

            for !success {
                    result, err = a.apiCall(reqMethod, reqPath, data)
                    if err == nil {
                            success = true
                    }

                    // break and return error if not using exponential backoff
                    if err != nil {
                            if !a.useExponentialBackoff {
                                    break
                            }
                            if matched, _ := regexp.MatchString("code 403", err.Error()); matched {
                                    break
                            }
                    }

                    if !success {
                            var wait float64
                            if attempts >= len(backoffs) {
                                    wait = backoff(backoffs[len(backoffs)-1])
                            } else {
                                    wait = backoff(backoffs[attempts])
                            }
                            attempts++
                            a.Log.Printf("[WARN] API call failed %s, retrying in %d seconds.\n", err.Error(), uint(wait))
                            time.Sleep(time.Duration(wait) * time.Second)
                    }
            }

            return result, err
    }

    // apiCall call Circonus API
    func (a *API) apiCall(reqMethod string, reqPath string, data []byte) ([]byte, error) {
            reqURL := a.apiURL.String()

            if reqPath == "" {
                    return nil, errors.New("Invalid URL path")
            }
            if reqPath[:1] != "/" {
                    reqURL += "/"
            }
            if len(reqPath) >= 3 && reqPath[:3] == "/v2" {
                    reqURL += reqPath[3:]
            } else {
                    reqURL += reqPath
            }

            // keep last HTTP error in the event of retry failure
            var lastHTTPError error
            retryPolicy := func(resp *http.Response, err error) (bool, error) {
                    if err != nil {
                            lastHTTPError = err
                            return true, err
                    }
                    // Check the response code. We retry on 500-range responses to allow
                    // the server time to recover, as 500's are typically not permanent
                    // errors and may relate to outages on the server side. This will catch
                    // invalid response codes as well, like 0 and 999.
                    // Retry on 429 (rate limit) as well.
                    if resp.StatusCode == 0 || // wtf?!
                            resp.StatusCode >= 500 || // rutroh
                            resp.StatusCode == 429 { // rate limit
                            body, readErr := ioutil.ReadAll(resp.Body)
                            if readErr != nil {
                                    lastHTTPError = fmt.Errorf("- response: %d %s", resp.StatusCode, readErr.Error())
                            } else {
                                    lastHTTPError = fmt.Errorf("- response: %d %s", resp.StatusCode, strings.TrimSpace(string(body)))
                            }
                            return true, nil
                    }
                    return false, nil
            }

            dataReader := bytes.NewReader(data)

            req, err := retryablehttp.NewRequest(reqMethod, reqURL, dataReader)
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] creating API request: %s %+v", reqURL, err)
            }
            req.Header.Add("Accept", "application/json")
            req.Header.Add("X-Circonus-Auth-Token", string(a.key))
            req.Header.Add("X-Circonus-App-Name", string(a.app))
            if string(a.accountID) != "" {
                    req.Header.Add("X-Circonus-Account-ID", string(a.accountID))
            }

            client := retryablehttp.NewClient()
            if a.apiURL.Scheme == "https" {
                    var tlscfg *tls.Config
                    if a.tlsConfig != nil { // preference full custom tls config
                            tlscfg = a.tlsConfig
                    } else if a.caCert != nil {
                            tlscfg = &tls.Config{RootCAs: a.caCert}
                    }
                    client.HTTPClient.Transport = &http.Transport{
                            Proxy: http.ProxyFromEnvironment,
                            Dial: (&net.Dialer{
                                    Timeout:   30 * time.Second,
                                    KeepAlive: 30 * time.Second,
                            }).Dial,
                            TLSHandshakeTimeout: 10 * time.Second,
                            TLSClientConfig:     tlscfg,
                            DisableKeepAlives:   true,
                            MaxIdleConnsPerHost: -1,
                            DisableCompression:  true,
                    }
            } else {
                    client.HTTPClient.Transport = &http.Transport{
                            Proxy: http.ProxyFromEnvironment,
                            Dial: (&net.Dialer{
                                    Timeout:   30 * time.Second,
                                    KeepAlive: 30 * time.Second,
                            }).Dial,
                            TLSHandshakeTimeout: 10 * time.Second,
                            DisableKeepAlives:   true,
                            MaxIdleConnsPerHost: -1,
                            DisableCompression:  true,
                    }
            }

            a.useExponentialBackoffmu.Lock()
            eb := a.useExponentialBackoff
            a.useExponentialBackoffmu.Unlock()

            if eb {
                    // limit to one request if using exponential backoff
                    client.RetryWaitMin = 1
                    client.RetryWaitMax = 2
                    client.RetryMax = 0
            } else {
                    client.RetryWaitMin = minRetryWait
                    client.RetryWaitMax = maxRetryWait
                    client.RetryMax = maxRetries
            }

            // retryablehttp only groks log or no log
            if a.Debug {
                    client.Logger = a.Log
            } else {
                    client.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
            }

            client.CheckRetry = retryPolicy

            resp, err := client.Do(req)
            if err != nil {
                    if lastHTTPError != nil {
                            return nil, lastHTTPError
                    }
                    return nil, fmt.Errorf("[ERROR] %s: %+v", reqURL, err)
            }

            defer resp.Body.Close()
            body, err := ioutil.ReadAll(resp.Body)
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] reading response %+v", err)
            }

            if resp.StatusCode < 200 || resp.StatusCode >= 300 {
                    msg := fmt.Sprintf("API response code %d: %s", resp.StatusCode, string(body))
                    if a.Debug {
                            a.Log.Printf("[DEBUG] %s\n", msg)
                    }

                    return nil, fmt.Errorf("[ERROR] %s", msg)
            }

            return body, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Broker API support - Fetch and Search
    // See: https://login.circonus.com/resources/api/calls/broker

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // BrokerDetail defines instance attributes
    type BrokerDetail struct {
            CN           string   `json:"cn"`                       // string
            ExternalHost *string  `json:"external_host"`            // string or null
            ExternalPort uint16   `json:"external_port"`            // uint16
            IP           *string  `json:"ipaddress"`                // string or null
            MinVer       uint     `json:"minimum_version_required"` // uint
            Modules      []string `json:"modules"`                  // [] len >= 0
            Port         *uint16  `json:"port"`                     // uint16 or null
            Skew         *string  `json:"skew"`                     // BUG doc: floating point number, api object: string or null
            Status       string   `json:"status"`                   // string
            Version      *uint    `json:"version"`                  // uint or null
    }

    // Broker defines a broker. See https://login.circonus.com/resources/api/calls/broker for more information.
    type Broker struct {
            CID       string         `json:"_cid"`       // string
            Details   []BrokerDetail `json:"_details"`   // [] len >= 1
            Latitude  *string        `json:"_latitude"`  // string or null
            Longitude *string        `json:"_longitude"` // string or null
            Name      string         `json:"_name"`      // string
            Tags      []string       `json:"_tags"`      // [] len >= 0
            Type      string         `json:"_type"`      // string
    }

    // FetchBroker retrieves broker with passed cid.
    func (a *API) FetchBroker(cid CIDType) (*Broker, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid broker CID [none]")
            }

            brokerCID := string(*cid)

            matched, err := regexp.MatchString(config.BrokerCIDRegex, brokerCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid broker CID [%s]", brokerCID)
            }

            result, err := a.Get(brokerCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch broker, received JSON: %s", string(result))
            }

            response := new(Broker)
            if err := json.Unmarshal(result, &response); err != nil {
                    return nil, err
            }

            return response, nil

    }

    // FetchBrokers returns all brokers available to the API Token.
    func (a *API) FetchBrokers() (*[]Broker, error) {
            result, err := a.Get(config.BrokerPrefix)
            if err != nil {
                    return nil, err
            }

            var response []Broker
            if err := json.Unmarshal(result, &response); err != nil {
                    return nil, err
            }

            return &response, nil
    }

    // SearchBrokers returns brokers matching the specified search
    // query and/or filter. If nil is passed for both parameters
    // all brokers will be returned.
    func (a *API) SearchBrokers(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Broker, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchBrokers()
            }

            reqURL := url.URL{
                    Path:     config.BrokerPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var brokers []Broker
            if err := json.Unmarshal(result, &brokers); err != nil {
                    return nil, err
            }

            return &brokers, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Check API support - Fetch and Search
    // See: https://login.circonus.com/resources/api/calls/check
    // Notes: checks do not directly support create, update, and delete - see check bundle.

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // CheckDetails contains [undocumented] check type specific information
    type CheckDetails map[config.Key]string

    // Check defines a check. See https://login.circonus.com/resources/api/calls/check for more information.
    type Check struct {
            Active         bool         `json:"_active"`       // bool
            BrokerCID      string       `json:"_broker"`       // string
            CheckBundleCID string       `json:"_check_bundle"` // string
            CheckUUID      string       `json:"_check_uuid"`   // string
            CID            string       `json:"_cid"`          // string
            Details        CheckDetails `json:"_details"`      // NOTE contents of details are check type specific, map len >= 0
    }

    // FetchCheck retrieves check with passed cid.
    func (a *API) FetchCheck(cid CIDType) (*Check, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid check CID [none]")
            }

            checkCID := string(*cid)

            matched, err := regexp.MatchString(config.CheckCIDRegex, checkCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid check CID [%s]", checkCID)
            }

            result, err := a.Get(checkCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch check, received JSON: %s", string(result))
            }

            check := new(Check)
            if err := json.Unmarshal(result, check); err != nil {
                    return nil, err
            }

            return check, nil
    }

    // FetchChecks retrieves all checks available to the API Token.
    func (a *API) FetchChecks() (*[]Check, error) {
            result, err := a.Get(config.CheckPrefix)
            if err != nil {
                    return nil, err
            }

            var checks []Check
            if err := json.Unmarshal(result, &checks); err != nil {
                    return nil, err
            }

            return &checks, nil
    }

    // SearchChecks returns checks matching the specified search query
    // and/or filter. If nil is passed for both parameters all checks
    // will be returned.
    func (a *API) SearchChecks(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Check, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchChecks()
            }

            reqURL := url.URL{
                    Path:     config.CheckPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, err
            }

            var checks []Check
            if err := json.Unmarshal(result, &checks); err != nil {
                    return nil, err
            }

            return &checks, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Check bundle API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/check_bundle

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // CheckBundleMetric individual metric configuration
    type CheckBundleMetric struct {
            Name   string   `json:"name"`             // string
            Result *string  `json:"result,omitempty"` // string or null, NOTE not settable - return/information value only
            Status string   `json:"status,omitempty"` // string
            Tags   []string `json:"tags"`             // [] len >= 0
            Type   string   `json:"type"`             // string
            Units  *string  `json:"units,omitempty"`  // string or null

    }

    // CheckBundleConfig contains the check type specific configuration settings
    // as k/v pairs (see https://login.circonus.com/resources/api/calls/check_bundle
    // for the specific settings available for each distinct check type)
    type CheckBundleConfig map[config.Key]string

    // CheckBundle defines a check bundle. See https://login.circonus.com/resources/api/calls/check_bundle for more information.
    type CheckBundle struct {
            Brokers            []string            `json:"brokers"`                            // [] len >= 0
            Checks             []string            `json:"_checks,omitempty"`                  // [] len >= 0
            CheckUUIDs         []string            `json:"_check_uuids,omitempty"`             // [] len >= 0
            CID                string              `json:"_cid,omitempty"`                     // string
            Config             CheckBundleConfig   `json:"config"`                             // NOTE contents of config are check type specific, map len >= 0
            Created            uint                `json:"_created,omitempty"`                 // uint
            DisplayName        string              `json:"display_name"`                       // string
            LastModifedBy      string              `json:"_last_modifed_by,omitempty"`         // string
            LastModified       uint                `json:"_last_modified,omitempty"`           // uint
            MetricLimit        int                 `json:"metric_limit,omitempty"`             // int
            Metrics            []CheckBundleMetric `json:"metrics"`                            // [] >= 0
            Notes              *string             `json:"notes,omitempty"`                    // string or null
            Period             uint                `json:"period,omitempty"`                   // uint
            ReverseConnectURLs []string            `json:"_reverse_connection_urls,omitempty"` // [] len >= 0
            Status             string              `json:"status,omitempty"`                   // string
            Tags               []string            `json:"tags,omitempty"`                     // [] len >= 0
            Target             string              `json:"target"`                             // string
            Timeout            float32             `json:"timeout,omitempty"`                  // float32
            Type               string              `json:"type"`                               // string
    }

    // NewCheckBundle returns new CheckBundle (with defaults, if applicable)
    func NewCheckBundle() *CheckBundle {
            return &CheckBundle{
                    Config:      make(CheckBundleConfig, config.DefaultConfigOptionsSize),
                    MetricLimit: config.DefaultCheckBundleMetricLimit,
                    Period:      config.DefaultCheckBundlePeriod,
                    Timeout:     config.DefaultCheckBundleTimeout,
                    Status:      config.DefaultCheckBundleStatus,
            }
    }

    // FetchCheckBundle retrieves check bundle with passed cid.
    func (a *API) FetchCheckBundle(cid CIDType) (*CheckBundle, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid check bundle CID [none]")
            }

            bundleCID := string(*cid)

            matched, err := regexp.MatchString(config.CheckBundleCIDRegex, bundleCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid check bundle CID [%v]", bundleCID)
            }

            result, err := a.Get(bundleCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch check bundle, received JSON: %s", string(result))
            }

            checkBundle := &CheckBundle{}
            if err := json.Unmarshal(result, checkBundle); err != nil {
                    return nil, err
            }

            return checkBundle, nil
    }

    // FetchCheckBundles retrieves all check bundles available to the API Token.
    func (a *API) FetchCheckBundles() (*[]CheckBundle, error) {
            result, err := a.Get(config.CheckBundlePrefix)
            if err != nil {
                    return nil, err
            }

            var checkBundles []CheckBundle
            if err := json.Unmarshal(result, &checkBundles); err != nil {
                    return nil, err
            }

            return &checkBundles, nil
    }

    // UpdateCheckBundle updates passed check bundle.
    func (a *API) UpdateCheckBundle(cfg *CheckBundle) (*CheckBundle, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid check bundle config [nil]")
            }

            bundleCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.CheckBundleCIDRegex, bundleCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid check bundle CID [%s]", bundleCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update check bundle, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(bundleCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            checkBundle := &CheckBundle{}
            if err := json.Unmarshal(result, checkBundle); err != nil {
                    return nil, err
            }

            return checkBundle, nil
    }

    // CreateCheckBundle creates a new check bundle (check).
    func (a *API) CreateCheckBundle(cfg *CheckBundle) (*CheckBundle, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid check bundle config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create check bundle, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.CheckBundlePrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            checkBundle := &CheckBundle{}
            if err := json.Unmarshal(result, checkBundle); err != nil {
                    return nil, err
            }

            return checkBundle, nil
    }

    // DeleteCheckBundle deletes passed check bundle.
    func (a *API) DeleteCheckBundle(cfg *CheckBundle) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid check bundle config [nil]")
            }
            return a.DeleteCheckBundleByCID(CIDType(&cfg.CID))
    }

    // DeleteCheckBundleByCID deletes check bundle with passed cid.
    func (a *API) DeleteCheckBundleByCID(cid CIDType) (bool, error) {

            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid check bundle CID [none]")
            }

            bundleCID := string(*cid)

            matched, err := regexp.MatchString(config.CheckBundleCIDRegex, bundleCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid check bundle CID [%v]", bundleCID)
            }

            _, err = a.Delete(bundleCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchCheckBundles returns check bundles matching the specified
    // search query and/or filter. If nil is passed for both parameters
    // all check bundles will be returned.
    func (a *API) SearchCheckBundles(searchCriteria *SearchQueryType, filterCriteria *map[string][]string) (*[]CheckBundle, error) {

            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchCheckBundles()
            }

            reqURL := url.URL{
                    Path:     config.CheckBundlePrefix,
                    RawQuery: q.Encode(),
            }

            resp, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var results []CheckBundle
            if err := json.Unmarshal(resp, &results); err != nil {
                    return nil, err
            }

            return &results, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // CheckBundleMetrics API support - Fetch, Create*, Update, and Delete**
    // See: https://login.circonus.com/resources/api/calls/check_bundle_metrics
    // *  : create metrics by adding to array with a status of 'active'
    // ** : delete (distable collection of) metrics by changing status from 'active' to 'available'

    package api

    import (
            "encoding/json"
            "fmt"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // CheckBundleMetrics defines metrics for a specific check bundle. See https://login.circonus.com/resources/api/calls/check_bundle_metrics for more information.
    type CheckBundleMetrics struct {
            CID     string              `json:"_cid,omitempty"` // string
            Metrics []CheckBundleMetric `json:"metrics"`        // See check_bundle.go for CheckBundleMetric definition
    }

    // FetchCheckBundleMetrics retrieves metrics for the check bundle with passed cid.
    func (a *API) FetchCheckBundleMetrics(cid CIDType) (*CheckBundleMetrics, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid check bundle metrics CID [none]")
            }

            metricsCID := string(*cid)

            matched, err := regexp.MatchString(config.CheckBundleMetricsCIDRegex, metricsCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid check bundle metrics CID [%s]", metricsCID)
            }

            result, err := a.Get(metricsCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch check bundle metrics, received JSON: %s", string(result))
            }

            metrics := &CheckBundleMetrics{}
            if err := json.Unmarshal(result, metrics); err != nil {
                    return nil, err
            }

            return metrics, nil
    }

    // UpdateCheckBundleMetrics updates passed metrics.
    func (a *API) UpdateCheckBundleMetrics(cfg *CheckBundleMetrics) (*CheckBundleMetrics, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid check bundle metrics config [nil]")
            }

            metricsCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.CheckBundleMetricsCIDRegex, metricsCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid check bundle metrics CID [%s]", metricsCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update check bundle metrics, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(metricsCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            metrics := &CheckBundleMetrics{}
            if err := json.Unmarshal(result, metrics); err != nil {
                    return nil, err
            }

            return metrics, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Contact Group API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/contact_group

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // ContactGroupAlertFormats define alert formats
    type ContactGroupAlertFormats struct {
            LongMessage  *string `json:"long_message"`  // string or null
            LongSubject  *string `json:"long_subject"`  // string or null
            LongSummary  *string `json:"long_summary"`  // string or null
            ShortMessage *string `json:"short_message"` // string or null
            ShortSummary *string `json:"short_summary"` // string or null
    }

    // ContactGroupContactsExternal external contacts
    type ContactGroupContactsExternal struct {
            Info   string `json:"contact_info"` // string
            Method string `json:"method"`       // string
    }

    // ContactGroupContactsUser user contacts
    type ContactGroupContactsUser struct {
            Info    string `json:"_contact_info,omitempty"` // string
            Method  string `json:"method"`                  // string
            UserCID string `json:"user"`                    // string
    }

    // ContactGroupContacts list of contacts
    type ContactGroupContacts struct {
            External []ContactGroupContactsExternal `json:"external"` // [] len >= 0
            Users    []ContactGroupContactsUser     `json:"users"`    // [] len >= 0
    }

    // ContactGroupEscalation defines escalations for severity levels
    type ContactGroupEscalation struct {
            After           uint   `json:"after"`         // uint
            ContactGroupCID string `json:"contact_group"` // string
    }

    // ContactGroup defines a contact group. See https://login.circonus.com/resources/api/calls/contact_group for more information.
    type ContactGroup struct {
            AggregationWindow uint                      `json:"aggregation_window,omitempty"` // uint
            AlertFormats      ContactGroupAlertFormats  `json:"alert_formats,omitempty"`      // ContactGroupAlertFormats
            CID               string                    `json:"_cid,omitempty"`               // string
            Contacts          ContactGroupContacts      `json:"contacts,omitempty"`           // ContactGroupContacts
            Escalations       []*ContactGroupEscalation `json:"escalations,omitempty"`        // [] len == 5, elements: ContactGroupEscalation or null
            LastModified      uint                      `json:"_last_modified,omitempty"`     // uint
            LastModifiedBy    string                    `json:"_last_modified_by,omitempty"`  // string
            Name              string                    `json:"name,omitempty"`               // string
            Reminders         []uint                    `json:"reminders,omitempty"`          // [] len == 5
            Tags              []string                  `json:"tags,omitempty"`               // [] len >= 0
    }

    // NewContactGroup returns a ContactGroup (with defaults, if applicable)
    func NewContactGroup() *ContactGroup {
            return &ContactGroup{
                    Escalations: make([]*ContactGroupEscalation, config.NumSeverityLevels),
                    Reminders:   make([]uint, config.NumSeverityLevels),
                    Contacts: ContactGroupContacts{
                            External: []ContactGroupContactsExternal{},
                            Users:    []ContactGroupContactsUser{},
                    },
            }
    }

    // FetchContactGroup retrieves contact group with passed cid.
    func (a *API) FetchContactGroup(cid CIDType) (*ContactGroup, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid contact group CID [none]")
            }

            groupCID := string(*cid)

            matched, err := regexp.MatchString(config.ContactGroupCIDRegex, groupCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid contact group CID [%s]", groupCID)
            }

            result, err := a.Get(groupCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch contact group, received JSON: %s", string(result))
            }

            group := new(ContactGroup)
            if err := json.Unmarshal(result, group); err != nil {
                    return nil, err
            }

            return group, nil
    }

    // FetchContactGroups retrieves all contact groups available to the API Token.
    func (a *API) FetchContactGroups() (*[]ContactGroup, error) {
            result, err := a.Get(config.ContactGroupPrefix)
            if err != nil {
                    return nil, err
            }

            var groups []ContactGroup
            if err := json.Unmarshal(result, &groups); err != nil {
                    return nil, err
            }

            return &groups, nil
    }

    // UpdateContactGroup updates passed contact group.
    func (a *API) UpdateContactGroup(cfg *ContactGroup) (*ContactGroup, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid contact group config [nil]")
            }

            groupCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.ContactGroupCIDRegex, groupCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid contact group CID [%s]", groupCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update contact group, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(groupCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            group := &ContactGroup{}
            if err := json.Unmarshal(result, group); err != nil {
                    return nil, err
            }

            return group, nil
    }

    // CreateContactGroup creates a new contact group.
    func (a *API) CreateContactGroup(cfg *ContactGroup) (*ContactGroup, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid contact group config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create contact group, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.ContactGroupPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            group := &ContactGroup{}
            if err := json.Unmarshal(result, group); err != nil {
                    return nil, err
            }

            return group, nil
    }

    // DeleteContactGroup deletes passed contact group.
    func (a *API) DeleteContactGroup(cfg *ContactGroup) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid contact group config [nil]")
            }
            return a.DeleteContactGroupByCID(CIDType(&cfg.CID))
    }

    // DeleteContactGroupByCID deletes contact group with passed cid.
    func (a *API) DeleteContactGroupByCID(cid CIDType) (bool, error) {
            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid contact group CID [none]")
            }

            groupCID := string(*cid)

            matched, err := regexp.MatchString(config.ContactGroupCIDRegex, groupCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid contact group CID [%s]", groupCID)
            }

            _, err = a.Delete(groupCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchContactGroups returns contact groups matching the specified
    // search query and/or filter. If nil is passed for both parameters
    // all contact groups will be returned.
    func (a *API) SearchContactGroups(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]ContactGroup, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchContactGroups()
            }

            reqURL := url.URL{
                    Path:     config.ContactGroupPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var groups []ContactGroup
            if err := json.Unmarshal(result, &groups); err != nil {
                    return nil, err
            }

            return &groups, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Dashboard API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/dashboard

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // DashboardGridLayout defines layout
    type DashboardGridLayout struct {
            Height uint `json:"height"`
            Width  uint `json:"width"`
    }

    // DashboardAccessConfig defines access config
    type DashboardAccessConfig struct {
            BlackDash           bool   `json:"black_dash,omitempty"`
            Enabled             bool   `json:"enabled,omitempty"`
            Fullscreen          bool   `json:"fullscreen,omitempty"`
            FullscreenHideTitle bool   `json:"fullscreen_hide_title,omitempty"`
            Nickname            string `json:"nickname,omitempty"`
            ScaleText           bool   `json:"scale_text,omitempty"`
            SharedID            string `json:"shared_id,omitempty"`
            TextSize            uint   `json:"text_size,omitempty"`
    }

    // DashboardOptions defines options
    type DashboardOptions struct {
            AccessConfigs       []DashboardAccessConfig `json:"access_configs,omitempty"`
            FullscreenHideTitle bool                    `json:"fullscreen_hide_title,omitempty"`
            HideGrid            bool                    `json:"hide_grid,omitempty"`
            Linkages            [][]string              `json:"linkages,omitempty"`
            ScaleText           bool                    `json:"scale_text,omitempty"`
            TextSize            uint                    `json:"text_size,omitempty"`
    }

    // ChartTextWidgetDatapoint defines datapoints for charts
    type ChartTextWidgetDatapoint struct {
            AccountID    string `json:"account_id,omitempty"`     // metric cluster, metric
            CheckID      uint   `json:"_check_id,omitempty"`      // metric
            ClusterID    uint   `json:"cluster_id,omitempty"`     // metric cluster
            ClusterTitle string `json:"_cluster_title,omitempty"` // metric cluster
            Label        string `json:"label,omitempty"`          // metric
            Label2       string `json:"_label,omitempty"`         // metric cluster
            Metric       string `json:"metric,omitempty"`         // metric
            MetricType   string `json:"_metric_type,omitempty"`   // metric
            NumericOnly  bool   `json:"numeric_only,omitempty"`   // metric cluster
    }

    // ChartWidgetDefinitionLegend defines chart widget definition legend
    type ChartWidgetDefinitionLegend struct {
            Show bool   `json:"show,omitempty"`
            Type string `json:"type,omitempty"`
    }

    // ChartWidgetWedgeLabels defines chart widget wedge labels
    type ChartWidgetWedgeLabels struct {
            OnChart  bool `json:"on_chart,omitempty"`
            ToolTips bool `json:"tooltips,omitempty"`
    }

    // ChartWidgetWedgeValues defines chart widget wedge values
    type ChartWidgetWedgeValues struct {
            Angle string `json:"angle,omitempty"`
            Color string `json:"color,omitempty"`
            Show  bool   `json:"show,omitempty"`
    }

    // ChartWidgtDefinition defines chart widget definition
    type ChartWidgtDefinition struct {
            Datasource        string                      `json:"datasource,omitempty"`
            Derive            string                      `json:"derive,omitempty"`
            DisableAutoformat bool                        `json:"disable_autoformat,omitempty"`
            Formula           string                      `json:"formula,omitempty"`
            Legend            ChartWidgetDefinitionLegend `json:"legend,omitempty"`
            Period            uint                        `json:"period,omitempty"`
            PopOnHover        bool                        `json:"pop_onhover,omitempty"`
            WedgeLabels       ChartWidgetWedgeLabels      `json:"wedge_labels,omitempty"`
            WedgeValues       ChartWidgetWedgeValues      `json:"wedge_values,omitempty"`
    }

    // ForecastGaugeWidgetThresholds defines forecast widget thresholds
    type ForecastGaugeWidgetThresholds struct {
            Colors []string `json:"colors,omitempty"` // forecasts, gauges
            Flip   bool     `json:"flip,omitempty"`   // gauges
            Values []string `json:"values,omitempty"` // forecasts, gauges
    }

    // StatusWidgetAgentStatusSettings defines agent status settings
    type StatusWidgetAgentStatusSettings struct {
            Search         string `json:"search,omitempty"`
            ShowAgentTypes string `json:"show_agent_types,omitempty"`
            ShowContact    bool   `json:"show_contact,omitempty"`
            ShowFeeds      bool   `json:"show_feeds,omitempty"`
            ShowSetup      bool   `json:"show_setup,omitempty"`
            ShowSkew       bool   `json:"show_skew,omitempty"`
            ShowUpdates    bool   `json:"show_updates,omitempty"`
    }

    // StatusWidgetHostStatusSettings defines host status settings
    type StatusWidgetHostStatusSettings struct {
            LayoutStyle  string   `json:"layout_style,omitempty"`
            Search       string   `json:"search,omitempty"`
            SortBy       string   `json:"sort_by,omitempty"`
            TagFilterSet []string `json:"tag_filter_set,omitempty"`
    }

    // DashboardWidgetSettings defines settings specific to widget
    type DashboardWidgetSettings struct {
            AccountID           string                          `json:"account_id,omitempty"`            // alerts, clusters, gauges, graphs, lists, status
            Acknowledged        string                          `json:"acknowledged,omitempty"`          // alerts
            AgentStatusSettings StatusWidgetAgentStatusSettings `json:"agent_status_settings,omitempty"` // status
            Algorithm           string                          `json:"algorithm,omitempty"`             // clusters
            Autoformat          bool                            `json:"autoformat,omitempty"`            // text
            BodyFormat          string                          `json:"body_format,omitempty"`           // text
            ChartType           string                          `json:"chart_type,omitempty"`            // charts
            CheckUUID           string                          `json:"check_uuid,omitempty"`            // gauges
            Cleared             string                          `json:"cleared,omitempty"`               // alerts
            ClusterID           uint                            `json:"cluster_id,omitempty"`            // clusters
            ClusterName         string                          `json:"cluster_name,omitempty"`          // clusters
            ContactGroups       []uint                          `json:"contact_groups,omitempty"`        // alerts
            ContentType         string                          `json:"content_type,omitempty"`          // status
            Datapoints          []ChartTextWidgetDatapoint      `json:"datapoints,omitempty"`            // charts, text
            DateWindow          string                          `json:"date_window,omitempty"`           // graphs
            Definition          ChartWidgtDefinition            `json:"definition,omitempty"`            // charts
            Dependents          string                          `json:"dependents,omitempty"`            // alerts
            DisableAutoformat   bool                            `json:"disable_autoformat,omitempty"`    // gauges
            Display             string                          `json:"display,omitempty"`               // alerts
            Format              string                          `json:"format,omitempty"`                // forecasts
            Formula             string                          `json:"formula,omitempty"`               // gauges
            GraphUUID           string                          `json:"graph_id,omitempty"`              // graphs
            HideXAxis           bool                            `json:"hide_xaxis,omitempty"`            // graphs
            HideYAxis           bool                            `json:"hide_yaxis,omitempty"`            // graphs
            HostStatusSettings  StatusWidgetHostStatusSettings  `json:"host_status_settings,omitempty"`  // status
            KeyInline           bool                            `json:"key_inline,omitempty"`            // graphs
            KeyLoc              string                          `json:"key_loc,omitempty"`               // graphs
            KeySize             uint                            `json:"key_size,omitempty"`              // graphs
            KeyWrap             bool                            `json:"key_wrap,omitempty"`              // graphs
            Label               string                          `json:"label,omitempty"`                 // graphs
            Layout              string                          `json:"layout,omitempty"`                // clusters
            Limit               uint                            `json:"limit,omitempty"`                 // lists
            Maintenance         string                          `json:"maintenance,omitempty"`           // alerts
            Markup              string                          `json:"markup,omitempty"`                // html
            MetricDisplayName   string                          `json:"metric_display_name,omitempty"`   // gauges
            MetricName          string                          `json:"metric_name,omitempty"`           // gauges
            MinAge              string                          `json:"min_age,omitempty"`               // alerts
            OffHours            []uint                          `json:"off_hours,omitempty"`             // alerts
            OverlaySetID        string                          `json:"overlay_set_id,omitempty"`        // graphs
            Period              uint                            `json:"period,omitempty"`                // gauges, text, graphs
            RangeHigh           int                             `json:"range_high,omitempty"`            // gauges
            RangeLow            int                             `json:"range_low,omitempty"`             // gauges
            Realtime            bool                            `json:"realtime,omitempty"`              // graphs
            ResourceLimit       string                          `json:"resource_limit,omitempty"`        // forecasts
            ResourceUsage       string                          `json:"resource_usage,omitempty"`        // forecasts
            Search              string                          `json:"search,omitempty"`                // alerts, lists
            Severity            string                          `json:"severity,omitempty"`              // alerts
            ShowFlags           bool                            `json:"show_flags,omitempty"`            // graphs
            Size                string                          `json:"size,omitempty"`                  // clusters
            TagFilterSet        []string                        `json:"tag_filter_set,omitempty"`        // alerts
            Threshold           float32                         `json:"threshold,omitempty"`             // clusters
            Thresholds          ForecastGaugeWidgetThresholds   `json:"thresholds,omitempty"`            // forecasts, gauges
            TimeWindow          string                          `json:"time_window,omitempty"`           // alerts
            Title               string                          `json:"title,omitempty"`                 // alerts, charts, forecasts, gauges, html
            TitleFormat         string                          `json:"title_format,omitempty"`          // text
            Trend               string                          `json:"trend,omitempty"`                 // forecasts
            Type                string                          `json:"type,omitempty"`                  // gauges, lists
            UseDefault          bool                            `json:"use_default,omitempty"`           // text
            ValueType           string                          `json:"value_type,omitempty"`            // gauges, text
            WeekDays            []string                        `json:"weekdays,omitempty"`              // alerts
    }

    // DashboardWidget defines widget
    type DashboardWidget struct {
            Active   bool                    `json:"active"`
            Height   uint                    `json:"height"`
            Name     string                  `json:"name"`
            Origin   string                  `json:"origin"`
            Settings DashboardWidgetSettings `json:"settings"`
            Type     string                  `json:"type"`
            WidgetID string                  `json:"widget_id"`
            Width    uint                    `json:"width"`
    }

    // Dashboard defines a dashboard. See https://login.circonus.com/resources/api/calls/dashboard for more information.
    type Dashboard struct {
            AccountDefault bool                `json:"account_default"`
            Active         bool                `json:"_active,omitempty"`
            CID            string              `json:"_cid,omitempty"`
            Created        uint                `json:"_created,omitempty"`
            CreatedBy      string              `json:"_created_by,omitempty"`
            GridLayout     DashboardGridLayout `json:"grid_layout"`
            LastModified   uint                `json:"_last_modified,omitempty"`
            Options        DashboardOptions    `json:"options"`
            Shared         bool                `json:"shared"`
            Title          string              `json:"title"`
            UUID           string              `json:"_dashboard_uuid,omitempty"`
            Widgets        []DashboardWidget   `json:"widgets"`
    }

    // NewDashboard returns a new Dashboard (with defaults, if applicable)
    func NewDashboard() *Dashboard {
            return &Dashboard{}
    }

    // FetchDashboard retrieves dashboard with passed cid.
    func (a *API) FetchDashboard(cid CIDType) (*Dashboard, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid dashboard CID [none]")
            }

            dashboardCID := string(*cid)

            matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
            }

            result, err := a.Get(string(*cid))
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch dashboard, received JSON: %s", string(result))
            }

            dashboard := new(Dashboard)
            if err := json.Unmarshal(result, dashboard); err != nil {
                    return nil, err
            }

            return dashboard, nil
    }

    // FetchDashboards retrieves all dashboards available to the API Token.
    func (a *API) FetchDashboards() (*[]Dashboard, error) {
            result, err := a.Get(config.DashboardPrefix)
            if err != nil {
                    return nil, err
            }

            var dashboards []Dashboard
            if err := json.Unmarshal(result, &dashboards); err != nil {
                    return nil, err
            }

            return &dashboards, nil
    }

    // UpdateDashboard updates passed dashboard.
    func (a *API) UpdateDashboard(cfg *Dashboard) (*Dashboard, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid dashboard config [nil]")
            }

            dashboardCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update dashboard, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(dashboardCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            dashboard := &Dashboard{}
            if err := json.Unmarshal(result, dashboard); err != nil {
                    return nil, err
            }

            return dashboard, nil
    }

    // CreateDashboard creates a new dashboard.
    func (a *API) CreateDashboard(cfg *Dashboard) (*Dashboard, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid dashboard config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create dashboard, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.DashboardPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            dashboard := &Dashboard{}
            if err := json.Unmarshal(result, dashboard); err != nil {
                    return nil, err
            }

            return dashboard, nil
    }

    // DeleteDashboard deletes passed dashboard.
    func (a *API) DeleteDashboard(cfg *Dashboard) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid dashboard config [nil]")
            }
            return a.DeleteDashboardByCID(CIDType(&cfg.CID))
    }

    // DeleteDashboardByCID deletes dashboard with passed cid.
    func (a *API) DeleteDashboardByCID(cid CIDType) (bool, error) {
            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid dashboard CID [none]")
            }

            dashboardCID := string(*cid)

            matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
            }

            _, err = a.Delete(dashboardCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchDashboards returns dashboards matching the specified
    // search query and/or filter. If nil is passed for both parameters
    // all dashboards will be returned.
    func (a *API) SearchDashboards(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Dashboard, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchDashboards()
            }

            reqURL := url.URL{
                    Path:     config.DashboardPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var dashboards []Dashboard
            if err := json.Unmarshal(result, &dashboards); err != nil {
                    return nil, err
            }

            return &dashboards, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Graph API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/graph

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // GraphAccessKey defines an access key for a graph
    type GraphAccessKey struct {
            Active         bool   `json:"active,omitempty"`           // boolean
            Height         uint   `json:"height,omitempty"`           // uint
            Key            string `json:"key,omitempty"`              // string
            Legend         bool   `json:"legend,omitempty"`           // boolean
            LockDate       bool   `json:"lock_date,omitempty"`        // boolean
            LockMode       string `json:"lock_mode,omitempty"`        // string
            LockRangeEnd   uint   `json:"lock_range_end,omitempty"`   // uint
            LockRangeStart uint   `json:"lock_range_start,omitempty"` // uint
            LockShowTimes  bool   `json:"lock_show_times,omitempty"`  // boolean
            LockZoom       string `json:"lock_zoom,omitempty"`        // string
            Nickname       string `json:"nickname,omitempty"`         // string
            Title          bool   `json:"title,omitempty"`            // boolean
            Width          uint   `json:"width,omitempty"`            // uint
            XLabels        bool   `json:"x_labels,omitempty"`         // boolean
            YLabels        bool   `json:"y_labels,omitempty"`         // boolean
    }

    // GraphComposite defines a composite
    type GraphComposite struct {
            Axis          string  `json:"axis,omitempty"`           // string
            Color         string  `json:"color,omitempty"`          // string
            DataFormula   *string `json:"data_formula,omitempty"`   // string or null
            Hidden        bool    `json:"hidden,omitempty"`         // boolean
            LegendFormula *string `json:"legend_formula,omitempty"` // string or null
            Name          string  `json:"name,omitempty"`           // string
            Stack         *uint   `json:"stack,omitempty"`          // uint or null
    }

    // GraphDatapoint defines a datapoint
    type GraphDatapoint struct {
            Alpha         *float64    `json:"alpha,string,omitempty"` // float64
            Axis          string      `json:"axis,omitempty"`         // string
            CAQL          *string     `json:"caql,omitempty"`         // string or null
            CheckID       uint        `json:"check_id,omitempty"`     // uint
            Color         *string     `json:"color,omitempty"`        // string
            DataFormula   *string     `json:"data_formula"`           // string or null
            Derive        interface{} `json:"derive,omitempty"`       // BUG doc: string, api: string or boolean(for caql statements)
            Hidden        bool        `json:"hidden"`                 // boolean
            LegendFormula *string     `json:"legend_formula"`         // string or null
            MetricName    string      `json:"metric_name,omitempty"`  // string
            MetricType    string      `json:"metric_type,omitempty"`  // string
            Name          string      `json:"name"`                   // string
            Stack         *uint       `json:"stack"`                  // uint or null
    }

    // GraphGuide defines a guide
    type GraphGuide struct {
            Color         string  `json:"color,omitempty"`          // string
            DataFormula   *string `json:"data_formula,omitempty"`   // string or null
            Hidden        bool    `json:"hidden,omitempty"`         // boolean
            LegendFormula *string `json:"legend_formula,omitempty"` // string or null
            Name          string  `json:"name,omitempty"`           // string
    }

    // GraphMetricCluster defines a metric cluster
    type GraphMetricCluster struct {
            AggregateFunc string  `json:"aggregate_function,omitempty"` // string
            Axis          string  `json:"axis,omitempty"`               // string
            Color         *string `json:"color,omitempty"`              // string
            DataFormula   *string `json:"data_formula"`                 // string or null
            Hidden        bool    `json:"hidden"`                       // boolean
            LegendFormula *string `json:"legend_formula"`               // string or null
            MetricCluster string  `json:"metric_cluster,omitempty"`     // string
            Name          string  `json:"name,omitempty"`               // string
            Stack         *uint   `json:"stack"`                        // uint or null
    }

    // OverlayDataOptions defines overlay options for data. Note, each overlay type requires
    // a _subset_ of the options. See Graph API documentation (URL above) for details.
    type OverlayDataOptions struct {
            Alerts        *int   `json:"alerts,string,omitempty"`         // int encoded as string BUG doc: numeric, api: string
            ArrayOutput   *int   `json:"array_output,string,omitempty"`   // int encoded as string BUG doc: numeric, api: string
            BasePeriod    *int   `json:"base_period,string,omitempty"`    // int encoded as string BUG doc: numeric, api: string
            Delay         *int   `json:"delay,string,omitempty"`          // int encoded as string BUG doc: numeric, api: string
            Extension     string `json:"extension,omitempty"`             // string
            GraphTitle    string `json:"graph_title,omitempty"`           // string
            GraphUUID     string `json:"graph_id,omitempty"`              // string
            InPercent     *bool  `json:"in_percent,string,omitempty"`     // boolean encoded as string BUG doc: boolean, api: string
            Inverse       *int   `json:"inverse,string,omitempty"`        // int encoded as string BUG doc: numeric, api: string
            Method        string `json:"method,omitempty"`                // string
            Model         string `json:"model,omitempty"`                 // string
            ModelEnd      string `json:"model_end,omitempty"`             // string
            ModelPeriod   string `json:"model_period,omitempty"`          // string
            ModelRelative *int   `json:"model_relative,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
            Out           string `json:"out,omitempty"`                   // string
            Prequel       string `json:"prequel,omitempty"`               // string
            Presets       string `json:"presets,omitempty"`               // string
            Quantiles     string `json:"quantiles,omitempty"`             // string
            SeasonLength  *int   `json:"season_length,string,omitempty"`  // int encoded as string BUG doc: numeric, api: string
            Sensitivity   *int   `json:"sensitivity,string,omitempty"`    // int encoded as string BUG doc: numeric, api: string
            SingleValue   *int   `json:"single_value,string,omitempty"`   // int encoded as string BUG doc: numeric, api: string
            TargetPeriod  string `json:"target_period,omitempty"`         // string
            TimeOffset    string `json:"time_offset,omitempty"`           // string
            TimeShift     *int   `json:"time_shift,string,omitempty"`     // int encoded as string BUG doc: numeric, api: string
            Transform     string `json:"transform,omitempty"`             // string
            Version       *int   `json:"version,string,omitempty"`        // int encoded as string BUG doc: numeric, api: string
            Window        *int   `json:"window,string,omitempty"`         // int encoded as string BUG doc: numeric, api: string
            XShift        string `json:"x_shift,omitempty"`               // string
    }

    // OverlayUISpecs defines UI specs for overlay
    type OverlayUISpecs struct {
            Decouple bool   `json:"decouple,omitempty"` // boolean
            ID       string `json:"id,omitempty"`       // string
            Label    string `json:"label,omitempty"`    // string
            Type     string `json:"type,omitempty"`     // string
            Z        *int   `json:"z,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
    }

    // GraphOverlaySet defines overlays for graph
    type GraphOverlaySet struct {
            DataOpts OverlayDataOptions `json:"data_opts,omitempty"` // OverlayDataOptions
            ID       string             `json:"id,omitempty"`        // string
            Title    string             `json:"title,omitempty"`     // string
            UISpecs  OverlayUISpecs     `json:"ui_specs,omitempty"`  // OverlayUISpecs
    }

    // Graph defines a graph. See https://login.circonus.com/resources/api/calls/graph for more information.
    type Graph struct {
            AccessKeys     []GraphAccessKey            `json:"access_keys,omitempty"`                // [] len >= 0
            CID            string                      `json:"_cid,omitempty"`                       // string
            Composites     []GraphComposite            `json:"composites,omitempty"`                 // [] len >= 0
            Datapoints     []GraphDatapoint            `json:"datapoints,omitempt"`                  // [] len >= 0
            Description    string                      `json:"description,omitempty"`                // string
            Guides         []GraphGuide                `json:"guides,omitempty"`                     // [] len >= 0
            LineStyle      *string                     `json:"line_style"`                           // string or null
            LogLeftY       *int                        `json:"logarithmic_left_y,string,omitempty"`  // int encoded as string or null BUG doc: number (not string)
            LogRightY      *int                        `json:"logarithmic_right_y,string,omitempty"` // int encoded as string or null BUG doc: number (not string)
            MaxLeftY       *float64                    `json:"max_left_y,string,omitempty"`          // float64 encoded as string or null BUG doc: number (not string)
            MaxRightY      *float64                    `json:"max_right_y,string,omitempty"`         // float64 encoded as string or null BUG doc: number (not string)
            MetricClusters []GraphMetricCluster        `json:"metric_clusters,omitempty"`            // [] len >= 0
            MinLeftY       *float64                    `json:"min_left_y,string,omitempty"`          // float64 encoded as string or null BUG doc: number (not string)
            MinRightY      *float64                    `json:"min_right_y,string,omitempty"`         // float64 encoded as string or null BUG doc: number (not string)
            Notes          *string                     `json:"notes,omitempty"`                      // string or null
            OverlaySets    *map[string]GraphOverlaySet `json:"overlay_sets,omitempty"`               // GroupOverLaySets or null
            Style          *string                     `json:"style"`                                // string or null
            Tags           []string                    `json:"tags,omitempty"`                       // [] len >= 0
            Title          string                      `json:"title,omitempty"`                      // string
    }

    // NewGraph returns a Graph (with defaults, if applicable)
    func NewGraph() *Graph {
            return &Graph{}
    }

    // FetchGraph retrieves graph with passed cid.
    func (a *API) FetchGraph(cid CIDType) (*Graph, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid graph CID [none]")
            }

            graphCID := string(*cid)

            matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid graph CID [%s]", graphCID)
            }

            result, err := a.Get(graphCID)
            if err != nil {
                    return nil, err
            }
            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch graph, received JSON: %s", string(result))
            }

            graph := new(Graph)
            if err := json.Unmarshal(result, graph); err != nil {
                    return nil, err
            }

            return graph, nil
    }

    // FetchGraphs retrieves all graphs available to the API Token.
    func (a *API) FetchGraphs() (*[]Graph, error) {
            result, err := a.Get(config.GraphPrefix)
            if err != nil {
                    return nil, err
            }

            var graphs []Graph
            if err := json.Unmarshal(result, &graphs); err != nil {
                    return nil, err
            }

            return &graphs, nil
    }

    // UpdateGraph updates passed graph.
    func (a *API) UpdateGraph(cfg *Graph) (*Graph, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid graph config [nil]")
            }

            graphCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid graph CID [%s]", graphCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update graph, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(graphCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            graph := &Graph{}
            if err := json.Unmarshal(result, graph); err != nil {
                    return nil, err
            }

            return graph, nil
    }

    // CreateGraph creates a new graph.
    func (a *API) CreateGraph(cfg *Graph) (*Graph, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid graph config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update graph, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.GraphPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            graph := &Graph{}
            if err := json.Unmarshal(result, graph); err != nil {
                    return nil, err
            }

            return graph, nil
    }

    // DeleteGraph deletes passed graph.
    func (a *API) DeleteGraph(cfg *Graph) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid graph config [nil]")
            }
            return a.DeleteGraphByCID(CIDType(&cfg.CID))
    }

    // DeleteGraphByCID deletes graph with passed cid.
    func (a *API) DeleteGraphByCID(cid CIDType) (bool, error) {
            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid graph CID [none]")
            }

            graphCID := string(*cid)

            matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid graph CID [%s]", graphCID)
            }

            _, err = a.Delete(graphCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchGraphs returns graphs matching the specified search query
    // and/or filter. If nil is passed for both parameters all graphs
    // will be returned.
    func (a *API) SearchGraphs(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Graph, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchGraphs()
            }

            reqURL := url.URL{
                    Path:     config.GraphPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var graphs []Graph
            if err := json.Unmarshal(result, &graphs); err != nil {
                    return nil, err
            }

            return &graphs, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Maintenance window API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/maintenance

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // Maintenance defines a maintenance window. See https://login.circonus.com/resources/api/calls/maintenance for more information.
    type Maintenance struct {
            CID        string      `json:"_cid,omitempty"`       // string
            Item       string      `json:"item,omitempty"`       // string
            Notes      string      `json:"notes,omitempty"`      // string
            Severities interface{} `json:"severities,omitempty"` // []string NOTE can be set with CSV string or []string
            Start      uint        `json:"start,omitempty"`      // uint
            Stop       uint        `json:"stop,omitempty"`       // uint
            Tags       []string    `json:"tags,omitempty"`       // [] len >= 0
            Type       string      `json:"type,omitempty"`       // string
    }

    // NewMaintenanceWindow returns a new Maintenance window (with defaults, if applicable)
    func NewMaintenanceWindow() *Maintenance {
            return &Maintenance{}
    }

    // FetchMaintenanceWindow retrieves maintenance [window] with passed cid.
    func (a *API) FetchMaintenanceWindow(cid CIDType) (*Maintenance, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid maintenance window CID [none]")
            }

            maintenanceCID := string(*cid)

            matched, err := regexp.MatchString(config.MaintenanceCIDRegex, maintenanceCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid maintenance window CID [%s]", maintenanceCID)
            }

            result, err := a.Get(maintenanceCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch maintenance window, received JSON: %s", string(result))
            }

            window := &Maintenance{}
            if err := json.Unmarshal(result, window); err != nil {
                    return nil, err
            }

            return window, nil
    }

    // FetchMaintenanceWindows retrieves all maintenance [windows] available to API Token.
    func (a *API) FetchMaintenanceWindows() (*[]Maintenance, error) {
            result, err := a.Get(config.MaintenancePrefix)
            if err != nil {
                    return nil, err
            }

            var windows []Maintenance
            if err := json.Unmarshal(result, &windows); err != nil {
                    return nil, err
            }

            return &windows, nil
    }

    // UpdateMaintenanceWindow updates passed maintenance [window].
    func (a *API) UpdateMaintenanceWindow(cfg *Maintenance) (*Maintenance, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid maintenance window config [nil]")
            }

            maintenanceCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.MaintenanceCIDRegex, maintenanceCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid maintenance window CID [%s]", maintenanceCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update maintenance window, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(maintenanceCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            window := &Maintenance{}
            if err := json.Unmarshal(result, window); err != nil {
                    return nil, err
            }

            return window, nil
    }

    // CreateMaintenanceWindow creates a new maintenance [window].
    func (a *API) CreateMaintenanceWindow(cfg *Maintenance) (*Maintenance, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid maintenance window config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create maintenance window, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.MaintenancePrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            window := &Maintenance{}
            if err := json.Unmarshal(result, window); err != nil {
                    return nil, err
            }

            return window, nil
    }

    // DeleteMaintenanceWindow deletes passed maintenance [window].
    func (a *API) DeleteMaintenanceWindow(cfg *Maintenance) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid maintenance window config [nil]")
            }
            return a.DeleteMaintenanceWindowByCID(CIDType(&cfg.CID))
    }

    // DeleteMaintenanceWindowByCID deletes maintenance [window] with passed cid.
    func (a *API) DeleteMaintenanceWindowByCID(cid CIDType) (bool, error) {
            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid maintenance window CID [none]")
            }

            maintenanceCID := string(*cid)

            matched, err := regexp.MatchString(config.MaintenanceCIDRegex, maintenanceCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid maintenance window CID [%s]", maintenanceCID)
            }

            _, err = a.Delete(maintenanceCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchMaintenanceWindows returns maintenance [windows] matching
    // the specified search query and/or filter. If nil is passed for
    // both parameters all maintenance [windows] will be returned.
    func (a *API) SearchMaintenanceWindows(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Maintenance, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchMaintenanceWindows()
            }

            reqURL := url.URL{
                    Path:     config.MaintenancePrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var windows []Maintenance
            if err := json.Unmarshal(result, &windows); err != nil {
                    return nil, err
            }

            return &windows, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Metric API support - Fetch, Create*, Update, Delete*, and Search
    // See: https://login.circonus.com/resources/api/calls/metric
    // *  : create and delete are handled via check_bundle or check_bundle_metrics

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // Metric defines a metric. See https://login.circonus.com/resources/api/calls/metric for more information.
    type Metric struct {
            Active         bool     `json:"_active,omitempty"`       // boolean
            CheckActive    bool     `json:"_check_active,omitempty"` // boolean
            CheckBundleCID string   `json:"_check_bundle,omitempty"` // string
            CheckCID       string   `json:"_check,omitempty"`        // string
            CheckTags      []string `json:"_check_tags,omitempty"`   // [] len >= 0
            CheckUUID      string   `json:"_check_uuid,omitempty"`   // string
            CID            string   `json:"_cid,omitempty"`          // string
            Histogram      string   `json:"_histogram,omitempty"`    // string
            Link           *string  `json:"link,omitempty"`          // string or null
            MetricName     string   `json:"_metric_name,omitempty"`  // string
            MetricType     string   `json:"_metric_type,omitempty"`  // string
            Notes          *string  `json:"notes,omitempty"`         // string or null
            Tags           []string `json:"tags,omitempty"`          // [] len >= 0
            Units          *string  `json:"units,omitempty"`         // string or null
    }

    // FetchMetric retrieves metric with passed cid.
    func (a *API) FetchMetric(cid CIDType) (*Metric, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid metric CID [none]")
            }

            metricCID := string(*cid)

            matched, err := regexp.MatchString(config.MetricCIDRegex, metricCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid metric CID [%s]", metricCID)
            }

            result, err := a.Get(metricCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch metric, received JSON: %s", string(result))
            }

            metric := &Metric{}
            if err := json.Unmarshal(result, metric); err != nil {
                    return nil, err
            }

            return metric, nil
    }

    // FetchMetrics retrieves all metrics available to API Token.
    func (a *API) FetchMetrics() (*[]Metric, error) {
            result, err := a.Get(config.MetricPrefix)
            if err != nil {
                    return nil, err
            }

            var metrics []Metric
            if err := json.Unmarshal(result, &metrics); err != nil {
                    return nil, err
            }

            return &metrics, nil
    }

    // UpdateMetric updates passed metric.
    func (a *API) UpdateMetric(cfg *Metric) (*Metric, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid metric config [nil]")
            }

            metricCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.MetricCIDRegex, metricCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid metric CID [%s]", metricCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update metric, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(metricCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            metric := &Metric{}
            if err := json.Unmarshal(result, metric); err != nil {
                    return nil, err
            }

            return metric, nil
    }

    // SearchMetrics returns metrics matching the specified search query
    // and/or filter. If nil is passed for both parameters all metrics
    // will be returned.
    func (a *API) SearchMetrics(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Metric, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchMetrics()
            }

            reqURL := url.URL{
                    Path:     config.MetricPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var metrics []Metric
            if err := json.Unmarshal(result, &metrics); err != nil {
                    return nil, err
            }

            return &metrics, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Metric Cluster API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/metric_cluster

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // MetricQuery object
    type MetricQuery struct {
            Query string `json:"query"`
            Type  string `json:"type"`
    }

    // MetricCluster defines a metric cluster. See https://login.circonus.com/resources/api/calls/metric_cluster for more information.
    type MetricCluster struct {
            CID                 string              `json:"_cid,omitempty"`                   // string
            Description         string              `json:"description"`                      // string
            MatchingMetrics     []string            `json:"_matching_metrics,omitempty"`      // [] len >= 1 (result info only, if query has extras - cannot be set)
            MatchingUUIDMetrics map[string][]string `json:"_matching_uuid_metrics,omitempty"` // [] len >= 1 (result info only, if query has extras - cannot be set)
            Name                string              `json:"name"`                             // string
            Queries             []MetricQuery       `json:"queries"`                          // [] len >= 1
            Tags                []string            `json:"tags"`                             // [] len >= 0
    }

    // NewMetricCluster returns a new MetricCluster (with defaults, if applicable)
    func NewMetricCluster() *MetricCluster {
            return &MetricCluster{}
    }

    // FetchMetricCluster retrieves metric cluster with passed cid.
    func (a *API) FetchMetricCluster(cid CIDType, extras string) (*MetricCluster, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid metric cluster CID [none]")
            }

            clusterCID := string(*cid)

            matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID)
            }

            reqURL := url.URL{
                    Path: clusterCID,
            }

            extra := ""
            switch extras {
            case "metrics":
                    extra = "_matching_metrics"
            case "uuids":
                    extra = "_matching_uuid_metrics"
            }

            if extra != "" {
                    q := url.Values{}
                    q.Set("extra", extra)
                    reqURL.RawQuery = q.Encode()
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch metric cluster, received JSON: %s", string(result))
            }

            cluster := &MetricCluster{}
            if err := json.Unmarshal(result, cluster); err != nil {
                    return nil, err
            }

            return cluster, nil
    }

    // FetchMetricClusters retrieves all metric clusters available to API Token.
    func (a *API) FetchMetricClusters(extras string) (*[]MetricCluster, error) {
            reqURL := url.URL{
                    Path: config.MetricClusterPrefix,
            }

            extra := ""
            switch extras {
            case "metrics":
                    extra = "_matching_metrics"
            case "uuids":
                    extra = "_matching_uuid_metrics"
            }

            if extra != "" {
                    q := url.Values{}
                    q.Set("extra", extra)
                    reqURL.RawQuery = q.Encode()
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, err
            }

            var clusters []MetricCluster
            if err := json.Unmarshal(result, &clusters); err != nil {
                    return nil, err
            }

            return &clusters, nil
    }

    // UpdateMetricCluster updates passed metric cluster.
    func (a *API) UpdateMetricCluster(cfg *MetricCluster) (*MetricCluster, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid metric cluster config [nil]")
            }

            clusterCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update metric cluster, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(clusterCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            cluster := &MetricCluster{}
            if err := json.Unmarshal(result, cluster); err != nil {
                    return nil, err
            }

            return cluster, nil
    }

    // CreateMetricCluster creates a new metric cluster.
    func (a *API) CreateMetricCluster(cfg *MetricCluster) (*MetricCluster, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid metric cluster config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create metric cluster, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.MetricClusterPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            cluster := &MetricCluster{}
            if err := json.Unmarshal(result, cluster); err != nil {
                    return nil, err
            }

            return cluster, nil
    }

    // DeleteMetricCluster deletes passed metric cluster.
    func (a *API) DeleteMetricCluster(cfg *MetricCluster) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid metric cluster config [nil]")
            }
            return a.DeleteMetricClusterByCID(CIDType(&cfg.CID))
    }

    // DeleteMetricClusterByCID deletes metric cluster with passed cid.
    func (a *API) DeleteMetricClusterByCID(cid CIDType) (bool, error) {
            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid metric cluster CID [none]")
            }

            clusterCID := string(*cid)

            matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID)
            }

            _, err = a.Delete(clusterCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchMetricClusters returns metric clusters matching the specified
    // search query and/or filter. If nil is passed for both parameters
    // all metric clusters will be returned.
    func (a *API) SearchMetricClusters(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]MetricCluster, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchMetricClusters("")
            }

            reqURL := url.URL{
                    Path:     config.MetricClusterPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var clusters []MetricCluster
            if err := json.Unmarshal(result, &clusters); err != nil {
                    return nil, err
            }

            return &clusters, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // OutlierReport API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/report

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // OutlierReport defines a outlier report. See https://login.circonus.com/resources/api/calls/report for more information.
    type OutlierReport struct {
            CID              string   `json:"_cid,omitempty"`              // string
            Config           string   `json:"config,omitempty"`            // string
            Created          uint     `json:"_created,omitempty"`          // uint
            CreatedBy        string   `json:"_created_by,omitempty"`       // string
            LastModified     uint     `json:"_last_modified,omitempty"`    // uint
            LastModifiedBy   string   `json:"_last_modified_by,omitempty"` // string
            MetricClusterCID string   `json:"metric_cluster,omitempty"`    // st ring
            Tags             []string `json:"tags,omitempty"`              // [] len >= 0
            Title            string   `json:"title,omitempty"`             // string
    }

    // NewOutlierReport returns a new OutlierReport (with defaults, if applicable)
    func NewOutlierReport() *OutlierReport {
            return &OutlierReport{}
    }

    // FetchOutlierReport retrieves outlier report with passed cid.
    func (a *API) FetchOutlierReport(cid CIDType) (*OutlierReport, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid outlier report CID [none]")
            }

            reportCID := string(*cid)

            matched, err := regexp.MatchString(config.OutlierReportCIDRegex, reportCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid outlier report CID [%s]", reportCID)
            }

            result, err := a.Get(reportCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch outlier report, received JSON: %s", string(result))
            }

            report := &OutlierReport{}
            if err := json.Unmarshal(result, report); err != nil {
                    return nil, err
            }

            return report, nil
    }

    // FetchOutlierReports retrieves all outlier reports available to API Token.
    func (a *API) FetchOutlierReports() (*[]OutlierReport, error) {
            result, err := a.Get(config.OutlierReportPrefix)
            if err != nil {
                    return nil, err
            }

            var reports []OutlierReport
            if err := json.Unmarshal(result, &reports); err != nil {
                    return nil, err
            }

            return &reports, nil
    }

    // UpdateOutlierReport updates passed outlier report.
    func (a *API) UpdateOutlierReport(cfg *OutlierReport) (*OutlierReport, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid outlier report config [nil]")
            }

            reportCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.OutlierReportCIDRegex, reportCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid outlier report CID [%s]", reportCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update outlier report, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(reportCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            report := &OutlierReport{}
            if err := json.Unmarshal(result, report); err != nil {
                    return nil, err
            }

            return report, nil
    }

    // CreateOutlierReport creates a new outlier report.
    func (a *API) CreateOutlierReport(cfg *OutlierReport) (*OutlierReport, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid outlier report config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create outlier report, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.OutlierReportPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            report := &OutlierReport{}
            if err := json.Unmarshal(result, report); err != nil {
                    return nil, err
            }

            return report, nil
    }

    // DeleteOutlierReport deletes passed outlier report.
    func (a *API) DeleteOutlierReport(cfg *OutlierReport) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid outlier report config [nil]")
            }
            return a.DeleteOutlierReportByCID(CIDType(&cfg.CID))
    }

    // DeleteOutlierReportByCID deletes outlier report with passed cid.
    func (a *API) DeleteOutlierReportByCID(cid CIDType) (bool, error) {
            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid outlier report CID [none]")
            }

            reportCID := string(*cid)

            matched, err := regexp.MatchString(config.OutlierReportCIDRegex, reportCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid outlier report CID [%s]", reportCID)
            }

            _, err = a.Delete(reportCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchOutlierReports returns outlier report matching the
    // specified search query and/or filter. If nil is passed for
    // both parameters all outlier report will be returned.
    func (a *API) SearchOutlierReports(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]OutlierReport, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchOutlierReports()
            }

            reqURL := url.URL{
                    Path:     config.OutlierReportPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var reports []OutlierReport
            if err := json.Unmarshal(result, &reports); err != nil {
                    return nil, err
            }

            return &reports, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // ProvisionBroker API support - Fetch, Create, and Update
    // See: https://login.circonus.com/resources/api/calls/provision_broker
    // Note that the provision_broker endpoint does not return standard cid format
    //      of '/object/item' (e.g. /provision_broker/abc-123) it just returns 'item'

    package api

    import (
            "encoding/json"
            "fmt"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // BrokerStratcon defines stratcons for broker
    type BrokerStratcon struct {
            CN   string `json:"cn,omitempty"`   // string
            Host string `json:"host,omitempty"` // string
            Port string `json:"port,omitempty"` // string
    }

    // ProvisionBroker defines a provision broker [request]. See https://login.circonus.com/resources/api/calls/provision_broker for more details.
    type ProvisionBroker struct {
            Cert                    string           `json:"_cert,omitempty"`                     // string
            CID                     string           `json:"_cid,omitempty"`                      // string
            CSR                     string           `json:"_csr,omitempty"`                      // string
            ExternalHost            string           `json:"external_host,omitempty"`             // string
            ExternalPort            string           `json:"external_port,omitempty"`             // string
            IPAddress               string           `json:"ipaddress,omitempty"`                 // string
            Latitude                string           `json:"latitude,omitempty"`                  // string
            Longitude               string           `json:"longitude,omitempty"`                 // string
            Name                    string           `json:"noit_name,omitempty"`                 // string
            Port                    string           `json:"port,omitempty"`                      // string
            PreferReverseConnection bool             `json:"prefer_reverse_connection,omitempty"` // boolean
            Rebuild                 bool             `json:"rebuild,omitempty"`                   // boolean
            Stratcons               []BrokerStratcon `json:"_stratcons,omitempty"`                // [] len >= 1
            Tags                    []string         `json:"tags,omitempty"`                      // [] len >= 0
    }

    // NewProvisionBroker returns a new ProvisionBroker (with defaults, if applicable)
    func NewProvisionBroker() *ProvisionBroker {
            return &ProvisionBroker{}
    }

    // FetchProvisionBroker retrieves provision broker [request] with passed cid.
    func (a *API) FetchProvisionBroker(cid CIDType) (*ProvisionBroker, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid provision broker request CID [none]")
            }

            brokerCID := string(*cid)

            matched, err := regexp.MatchString(config.ProvisionBrokerCIDRegex, brokerCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid provision broker request CID [%s]", brokerCID)
            }

            result, err := a.Get(brokerCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch broker provision request, received JSON: %s", string(result))
            }

            broker := &ProvisionBroker{}
            if err := json.Unmarshal(result, broker); err != nil {
                    return nil, err
            }

            return broker, nil
    }

    // UpdateProvisionBroker updates a broker definition [request].
    func (a *API) UpdateProvisionBroker(cid CIDType, cfg *ProvisionBroker) (*ProvisionBroker, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid provision broker request config [nil]")
            }

            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid provision broker request CID [none]")
            }

            brokerCID := string(*cid)

            matched, err := regexp.MatchString(config.ProvisionBrokerCIDRegex, brokerCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid provision broker request CID [%s]", brokerCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update broker provision request, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(brokerCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            broker := &ProvisionBroker{}
            if err := json.Unmarshal(result, broker); err != nil {
                    return nil, err
            }

            return broker, nil
    }

    // CreateProvisionBroker creates a new provison broker [request].
    func (a *API) CreateProvisionBroker(cfg *ProvisionBroker) (*ProvisionBroker, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid provision broker request config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create broker provision request, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.ProvisionBrokerPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            broker := &ProvisionBroker{}
            if err := json.Unmarshal(result, broker); err != nil {
                    return nil, err
            }

            return broker, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Rule Set API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/rule_set

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // RuleSetRule defines a ruleset rule
    type RuleSetRule struct {
            Criteria          string      `json:"criteria"`                     // string
            Severity          uint        `json:"severity"`                     // uint
            Value             interface{} `json:"value"`                        // BUG doc: string, api: actual type returned switches based on Criteria
            Wait              uint        `json:"wait"`                         // uint
            WindowingDuration uint        `json:"windowing_duration,omitempty"` // uint
            WindowingFunction *string     `json:"windowing_function,omitempty"` // string or null
    }

    // RuleSet defines a ruleset. See https://login.circonus.com/resources/api/calls/rule_set for more information.
    type RuleSet struct {
            CheckCID      string             `json:"check"`            // string
            CID           string             `json:"_cid,omitempty"`   // string
            ContactGroups map[uint8][]string `json:"contact_groups"`   // [] len 5
            Derive        *string            `json:"derive,omitempty"` // string or null
            Link          *string            `json:"link"`             // string or null
            MetricName    string             `json:"metric_name"`      // string
            MetricTags    []string           `json:"metric_tags"`      // [] len >= 0
            MetricType    string             `json:"metric_type"`      // string
            Notes         *string            `json:"notes"`            // string or null
            Parent        *string            `json:"parent,omitempty"` // string or null
            Rules         []RuleSetRule      `json:"rules"`            // [] len >= 1
            Tags          []string           `json:"tags"`             // [] len >= 0
    }

    // NewRuleSet returns a new RuleSet (with defaults if applicable)
    func NewRuleSet() *RuleSet {
            return &RuleSet{}
    }

    // FetchRuleSet retrieves rule set with passed cid.
    func (a *API) FetchRuleSet(cid CIDType) (*RuleSet, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid rule set CID [none]")
            }

            rulesetCID := string(*cid)

            matched, err := regexp.MatchString(config.RuleSetCIDRegex, rulesetCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid rule set CID [%s]", rulesetCID)
            }

            result, err := a.Get(rulesetCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch rule set, received JSON: %s", string(result))
            }

            ruleset := &RuleSet{}
            if err := json.Unmarshal(result, ruleset); err != nil {
                    return nil, err
            }

            return ruleset, nil
    }

    // FetchRuleSets retrieves all rule sets available to API Token.
    func (a *API) FetchRuleSets() (*[]RuleSet, error) {
            result, err := a.Get(config.RuleSetPrefix)
            if err != nil {
                    return nil, err
            }

            var rulesets []RuleSet
            if err := json.Unmarshal(result, &rulesets); err != nil {
                    return nil, err
            }

            return &rulesets, nil
    }

    // UpdateRuleSet updates passed rule set.
    func (a *API) UpdateRuleSet(cfg *RuleSet) (*RuleSet, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid rule set config [nil]")
            }

            rulesetCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.RuleSetCIDRegex, rulesetCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid rule set CID [%s]", rulesetCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update rule set, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(rulesetCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            ruleset := &RuleSet{}
            if err := json.Unmarshal(result, ruleset); err != nil {
                    return nil, err
            }

            return ruleset, nil
    }

    // CreateRuleSet creates a new rule set.
    func (a *API) CreateRuleSet(cfg *RuleSet) (*RuleSet, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid rule set config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create rule set, sending JSON: %s", string(jsonCfg))
            }

            resp, err := a.Post(config.RuleSetPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            ruleset := &RuleSet{}
            if err := json.Unmarshal(resp, ruleset); err != nil {
                    return nil, err
            }

            return ruleset, nil
    }

    // DeleteRuleSet deletes passed rule set.
    func (a *API) DeleteRuleSet(cfg *RuleSet) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid rule set config [nil]")
            }
            return a.DeleteRuleSetByCID(CIDType(&cfg.CID))
    }

    // DeleteRuleSetByCID deletes rule set with passed cid.
    func (a *API) DeleteRuleSetByCID(cid CIDType) (bool, error) {
            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid rule set CID [none]")
            }

            rulesetCID := string(*cid)

            matched, err := regexp.MatchString(config.RuleSetCIDRegex, rulesetCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid rule set CID [%s]", rulesetCID)
            }

            _, err = a.Delete(rulesetCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchRuleSets returns rule sets matching the specified search
    // query and/or filter. If nil is passed for both parameters all
    // rule sets will be returned.
    func (a *API) SearchRuleSets(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]RuleSet, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchRuleSets()
            }

            reqURL := url.URL{
                    Path:     config.RuleSetPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var rulesets []RuleSet
            if err := json.Unmarshal(result, &rulesets); err != nil {
                    return nil, err
            }

            return &rulesets, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // RuleSetGroup API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/rule_set_group

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // RuleSetGroupFormula defines a formula for raising alerts
    type RuleSetGroupFormula struct {
            Expression    interface{} `json:"expression"`     // string or uint BUG doc: string, api: string or numeric
            RaiseSeverity uint        `json:"raise_severity"` // uint
            Wait          uint        `json:"wait"`           // uint
    }

    // RuleSetGroupCondition defines conditions for raising alerts
    type RuleSetGroupCondition struct {
            MatchingSeverities []string `json:"matching_serverities"` // [] len >= 1
            RuleSetCID         string   `json:"rule_set"`             // string
    }

    // RuleSetGroup defines a ruleset group. See https://login.circonus.com/resources/api/calls/rule_set_group for more information.
    type RuleSetGroup struct {
            CID               string                  `json:"_cid,omitempty"`      // string
            ContactGroups     map[uint8][]string      `json:"contact_groups"`      // [] len == 5
            Formulas          []RuleSetGroupFormula   `json:"formulas"`            // [] len >= 0
            Name              string                  `json:"name"`                // string
            RuleSetConditions []RuleSetGroupCondition `json:"rule_set_conditions"` // [] len >= 1
            Tags              []string                `json:"tags"`                // [] len >= 0
    }

    // NewRuleSetGroup returns a new RuleSetGroup (with defaults, if applicable)
    func NewRuleSetGroup() *RuleSetGroup {
            return &RuleSetGroup{}
    }

    // FetchRuleSetGroup retrieves rule set group with passed cid.
    func (a *API) FetchRuleSetGroup(cid CIDType) (*RuleSetGroup, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid rule set group CID [none]")
            }

            groupCID := string(*cid)

            matched, err := regexp.MatchString(config.RuleSetGroupCIDRegex, groupCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid rule set group CID [%s]", groupCID)
            }

            result, err := a.Get(groupCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch rule set group, received JSON: %s", string(result))
            }

            rulesetGroup := &RuleSetGroup{}
            if err := json.Unmarshal(result, rulesetGroup); err != nil {
                    return nil, err
            }

            return rulesetGroup, nil
    }

    // FetchRuleSetGroups retrieves all rule set groups available to API Token.
    func (a *API) FetchRuleSetGroups() (*[]RuleSetGroup, error) {
            result, err := a.Get(config.RuleSetGroupPrefix)
            if err != nil {
                    return nil, err
            }

            var rulesetGroups []RuleSetGroup
            if err := json.Unmarshal(result, &rulesetGroups); err != nil {
                    return nil, err
            }

            return &rulesetGroups, nil
    }

    // UpdateRuleSetGroup updates passed rule set group.
    func (a *API) UpdateRuleSetGroup(cfg *RuleSetGroup) (*RuleSetGroup, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid rule set group config [nil]")
            }

            groupCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.RuleSetGroupCIDRegex, groupCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid rule set group CID [%s]", groupCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update rule set group, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(groupCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            groups := &RuleSetGroup{}
            if err := json.Unmarshal(result, groups); err != nil {
                    return nil, err
            }

            return groups, nil
    }

    // CreateRuleSetGroup creates a new rule set group.
    func (a *API) CreateRuleSetGroup(cfg *RuleSetGroup) (*RuleSetGroup, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid rule set group config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create rule set group, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.RuleSetGroupPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            group := &RuleSetGroup{}
            if err := json.Unmarshal(result, group); err != nil {
                    return nil, err
            }

            return group, nil
    }

    // DeleteRuleSetGroup deletes passed rule set group.
    func (a *API) DeleteRuleSetGroup(cfg *RuleSetGroup) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid rule set group config [nil]")
            }
            return a.DeleteRuleSetGroupByCID(CIDType(&cfg.CID))
    }

    // DeleteRuleSetGroupByCID deletes rule set group wiht passed cid.
    func (a *API) DeleteRuleSetGroupByCID(cid CIDType) (bool, error) {
            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid rule set group CID [none]")
            }

            groupCID := string(*cid)

            matched, err := regexp.MatchString(config.RuleSetGroupCIDRegex, groupCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid rule set group CID [%s]", groupCID)
            }

            _, err = a.Delete(groupCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchRuleSetGroups returns rule set groups matching the
    // specified search query and/or filter. If nil is passed for
    // both parameters all rule set groups will be returned.
    func (a *API) SearchRuleSetGroups(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]RuleSetGroup, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchRuleSetGroups()
            }

            reqURL := url.URL{
                    Path:     config.RuleSetGroupPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var groups []RuleSetGroup
            if err := json.Unmarshal(result, &groups); err != nil {
                    return nil, err
            }

            return &groups, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // User API support - Fetch, Update, and Search
    // See: https://login.circonus.com/resources/api/calls/user
    // Note: Create and Delete are not supported directly via the User API
    // endpoint. See the Account endpoint for inviting and removing users
    // from specific accounts.

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // UserContactInfo defines known contact details
    type UserContactInfo struct {
            SMS  string `json:"sms,omitempty"`  // string
            XMPP string `json:"xmpp,omitempty"` // string
    }

    // User defines a user. See https://login.circonus.com/resources/api/calls/user for more information.
    type User struct {
            CID         string          `json:"_cid,omitempty"`         // string
            ContactInfo UserContactInfo `json:"contact_info,omitempty"` // UserContactInfo
            Email       string          `json:"email"`                  // string
            Firstname   string          `json:"firstname"`              // string
            Lastname    string          `json:"lastname"`               // string
    }

    // FetchUser retrieves user with passed cid. Pass nil for '/user/current'.
    func (a *API) FetchUser(cid CIDType) (*User, error) {
            var userCID string

            if cid == nil || *cid == "" {
                    userCID = config.UserPrefix + "/current"
            } else {
                    userCID = string(*cid)
            }

            matched, err := regexp.MatchString(config.UserCIDRegex, userCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid user CID [%s]", userCID)
            }

            result, err := a.Get(userCID)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch user, received JSON: %s", string(result))
            }

            user := new(User)
            if err := json.Unmarshal(result, user); err != nil {
                    return nil, err
            }

            return user, nil
    }

    // FetchUsers retrieves all users available to API Token.
    func (a *API) FetchUsers() (*[]User, error) {
            result, err := a.Get(config.UserPrefix)
            if err != nil {
                    return nil, err
            }

            var users []User
            if err := json.Unmarshal(result, &users); err != nil {
                    return nil, err
            }

            return &users, nil
    }

    // UpdateUser updates passed user.
    func (a *API) UpdateUser(cfg *User) (*User, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid user config [nil]")
            }

            userCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.UserCIDRegex, userCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid user CID [%s]", userCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update user, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(userCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            user := &User{}
            if err := json.Unmarshal(result, user); err != nil {
                    return nil, err
            }

            return user, nil
    }

    // SearchUsers returns users matching a filter (search queries
    // are not suppoted by the user endpoint). Pass nil as filter for all
    // users available to the API Token.
    func (a *API) SearchUsers(filterCriteria *SearchFilterType) (*[]User, error) {
            q := url.Values{}

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchUsers()
            }

            reqURL := url.URL{
                    Path:     config.UserPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var users []User
            if err := json.Unmarshal(result, &users); err != nil {
                    return nil, err
            }

            return &users, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Worksheet API support - Fetch, Create, Update, Delete, and Search
    // See: https://login.circonus.com/resources/api/calls/worksheet

    package api

    import (
            "encoding/json"
            "fmt"
            "net/url"
            "regexp"

            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // WorksheetGraph defines a worksheet cid to be include in the worksheet
    type WorksheetGraph struct {
            GraphCID string `json:"graph"` // string
    }

    // WorksheetSmartQuery defines a query to include multiple worksheets
    type WorksheetSmartQuery struct {
            Name  string   `json:"name"`
            Order []string `json:"order"`
            Query string   `json:"query"`
    }

    // Worksheet defines a worksheet. See https://login.circonus.com/resources/api/calls/worksheet for more information.
    type Worksheet struct {
            CID          string                `json:"_cid,omitempty"`          // string
            Description  *string               `json:"description"`             // string or null
            Favorite     bool                  `json:"favorite"`                // boolean
            Graphs       []WorksheetGraph      `json:"worksheets,omitempty"`    // [] len >= 0
            Notes        *string               `json:"notes"`                   // string or null
            SmartQueries []WorksheetSmartQuery `json:"smart_queries,omitempty"` // [] len >= 0
            Tags         []string              `json:"tags"`                    // [] len >= 0
            Title        string                `json:"title"`                   // string
    }

    // NewWorksheet returns a new Worksheet (with defaults, if applicable)
    func NewWorksheet() *Worksheet {
            return &Worksheet{}
    }

    // FetchWorksheet retrieves worksheet with passed cid.
    func (a *API) FetchWorksheet(cid CIDType) (*Worksheet, error) {
            if cid == nil || *cid == "" {
                    return nil, fmt.Errorf("Invalid worksheet CID [none]")
            }

            worksheetCID := string(*cid)

            matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
            }

            result, err := a.Get(string(*cid))
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] fetch worksheet, received JSON: %s", string(result))
            }

            worksheet := new(Worksheet)
            if err := json.Unmarshal(result, worksheet); err != nil {
                    return nil, err
            }

            return worksheet, nil
    }

    // FetchWorksheets retrieves all worksheets available to API Token.
    func (a *API) FetchWorksheets() (*[]Worksheet, error) {
            result, err := a.Get(config.WorksheetPrefix)
            if err != nil {
                    return nil, err
            }

            var worksheets []Worksheet
            if err := json.Unmarshal(result, &worksheets); err != nil {
                    return nil, err
            }

            return &worksheets, nil
    }

    // UpdateWorksheet updates passed worksheet.
    func (a *API) UpdateWorksheet(cfg *Worksheet) (*Worksheet, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid worksheet config [nil]")
            }

            worksheetCID := string(cfg.CID)

            matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
            if err != nil {
                    return nil, err
            }
            if !matched {
                    return nil, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] update worksheet, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Put(worksheetCID, jsonCfg)
            if err != nil {
                    return nil, err
            }

            worksheet := &Worksheet{}
            if err := json.Unmarshal(result, worksheet); err != nil {
                    return nil, err
            }

            return worksheet, nil
    }

    // CreateWorksheet creates a new worksheet.
    func (a *API) CreateWorksheet(cfg *Worksheet) (*Worksheet, error) {
            if cfg == nil {
                    return nil, fmt.Errorf("Invalid worksheet config [nil]")
            }

            jsonCfg, err := json.Marshal(cfg)
            if err != nil {
                    return nil, err
            }

            if a.Debug {
                    a.Log.Printf("[DEBUG] create annotation, sending JSON: %s", string(jsonCfg))
            }

            result, err := a.Post(config.WorksheetPrefix, jsonCfg)
            if err != nil {
                    return nil, err
            }

            worksheet := &Worksheet{}
            if err := json.Unmarshal(result, worksheet); err != nil {
                    return nil, err
            }

            return worksheet, nil
    }

    // DeleteWorksheet deletes passed worksheet.
    func (a *API) DeleteWorksheet(cfg *Worksheet) (bool, error) {
            if cfg == nil {
                    return false, fmt.Errorf("Invalid worksheet config [nil]")
            }
            return a.DeleteWorksheetByCID(CIDType(&cfg.CID))
    }

    // DeleteWorksheetByCID deletes worksheet with passed cid.
    func (a *API) DeleteWorksheetByCID(cid CIDType) (bool, error) {
            if cid == nil || *cid == "" {
                    return false, fmt.Errorf("Invalid worksheet CID [none]")
            }

            worksheetCID := string(*cid)

            matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
            if err != nil {
                    return false, err
            }
            if !matched {
                    return false, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
            }

            _, err = a.Delete(worksheetCID)
            if err != nil {
                    return false, err
            }

            return true, nil
    }

    // SearchWorksheets returns worksheets matching the specified search
    // query and/or filter. If nil is passed for both parameters all
    // worksheets will be returned.
    func (a *API) SearchWorksheets(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Worksheet, error) {
            q := url.Values{}

            if searchCriteria != nil && *searchCriteria != "" {
                    q.Set("search", string(*searchCriteria))
            }

            if filterCriteria != nil && len(*filterCriteria) > 0 {
                    for filter, criteria := range *filterCriteria {
                            for _, val := range criteria {
                                    q.Add(filter, val)
                            }
                    }
            }

            if q.Encode() == "" {
                    return a.FetchWorksheets()
            }

            reqURL := url.URL{
                    Path:     config.WorksheetPrefix,
                    RawQuery: q.Encode(),
            }

            result, err := a.Get(reqURL.String())
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] API call error %+v", err)
            }

            var worksheets []Worksheet
            if err := json.Unmarshal(result, &worksheets); err != nil {
                    return nil, err
            }

            return &worksheets, nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package checkmgr

    import (
            "fmt"
            "math/rand"
            "net"
            "net/url"
            "reflect"
            "strconv"
            "strings"
            "time"

            "github.com/circonus-labs/circonus-gometrics/api"
    )

    func init() {
            rand.Seed(time.Now().UnixNano())
    }

    // Get Broker to use when creating a check
    func (cm *CheckManager) getBroker() (*api.Broker, error) {
            if cm.brokerID != 0 {
                    cid := fmt.Sprintf("/broker/%d", cm.brokerID)
                    broker, err := cm.apih.FetchBroker(api.CIDType(&cid))
                    if err != nil {
                            return nil, err
                    }
                    if !cm.isValidBroker(broker) {
                            return nil, fmt.Errorf(
                                    "[ERROR] designated broker %d [%s] is invalid (not active, does not support required check type, or connectivity issue)",
                                    cm.brokerID,
                                    broker.Name)
                    }
                    return broker, nil
            }
            broker, err := cm.selectBroker()
            if err != nil {
                    return nil, fmt.Errorf("[ERROR] Unable to fetch suitable broker %s", err)
            }
            return broker, nil
    }

    // Get CN of Broker associated with submission_url to satisfy no IP SANS in certs
    func (cm *CheckManager) getBrokerCN(broker *api.Broker, submissionURL api.URLType) (string, error) {
            u, err := url.Parse(string(submissionURL))
            if err != nil {
                    return "", err
            }

            hostParts := strings.Split(u.Host, ":")
            host := hostParts[0]

            if net.ParseIP(host) == nil { // it's a non-ip string
                    return u.Host, nil
            }

            cn := ""

            for _, detail := range broker.Details {
                    if *detail.IP == host {
                            cn = detail.CN
                            break
                    }
            }

            if cn == "" {
                    return "", fmt.Errorf("[ERROR] Unable to match URL host (%s) to Broker", u.Host)
            }

            return cn, nil

    }

    // Select a broker for use when creating a check, if a specific broker
    // was not specified.
    func (cm *CheckManager) selectBroker() (*api.Broker, error) {
            var brokerList *[]api.Broker
            var err error

            if len(cm.brokerSelectTag) > 0 {
                    filter := api.SearchFilterType{
                            "f__tags_has": cm.brokerSelectTag,
                    }
                    brokerList, err = cm.apih.SearchBrokers(nil, &filter)
                    if err != nil {
                            return nil, err
                    }
            } else {
                    brokerList, err = cm.apih.FetchBrokers()
                    if err != nil {
                            return nil, err
                    }
            }

            if len(*brokerList) == 0 {
                    return nil, fmt.Errorf("zero brokers found")
            }

            validBrokers := make(map[string]api.Broker)
            haveEnterprise := false

            for _, broker := range *brokerList {
                    broker := broker
                    if cm.isValidBroker(&broker) {
                            validBrokers[broker.CID] = broker
                            if broker.Type == "enterprise" {
                                    haveEnterprise = true
                            }
                    }
            }

            if haveEnterprise { // eliminate non-enterprise brokers from valid brokers
                    for k, v := range validBrokers {
                            if v.Type != "enterprise" {
                                    delete(validBrokers, k)
                            }
                    }
            }

            if len(validBrokers) == 0 {
                    return nil, fmt.Errorf("found %d broker(s), zero are valid", len(*brokerList))
            }

            validBrokerKeys := reflect.ValueOf(validBrokers).MapKeys()
            selectedBroker := validBrokers[validBrokerKeys[rand.Intn(len(validBrokerKeys))].String()]

            if cm.Debug {
                    cm.Log.Printf("[DEBUG] Selected broker '%s'\n", selectedBroker.Name)
            }

            return &selectedBroker, nil

    }

    // Verify broker supports the check type to be used
    func (cm *CheckManager) brokerSupportsCheckType(checkType CheckTypeType, details *api.BrokerDetail) bool {

            baseType := string(checkType)

            for _, module := range details.Modules {
                    if module == baseType {
                            return true
                    }
            }

            if idx := strings.Index(baseType, ":"); idx > 0 {
                    baseType = baseType[0:idx]
            }

            for _, module := range details.Modules {
                    if module == baseType {
                            return true
                    }
            }

            return false

    }

    // Is the broker valid (active, supports check type, and reachable)
    func (cm *CheckManager) isValidBroker(broker *api.Broker) bool {
            var brokerHost string
            var brokerPort string
            valid := false
            for _, detail := range broker.Details {
                    detail := detail

                    // broker must be active
                    if detail.Status != statusActive {
                            if cm.Debug {
                                    cm.Log.Printf("[DEBUG] Broker '%s' is not active.\n", broker.Name)
                            }
                            continue
                    }

                    // broker must have module loaded for the check type to be used
                    if !cm.brokerSupportsCheckType(cm.checkType, &detail) {
                            if cm.Debug {
                                    cm.Log.Printf("[DEBUG] Broker '%s' does not support '%s' checks.\n", broker.Name, cm.checkType)
                            }
                            continue
                    }

                    if detail.ExternalPort != 0 {
                            brokerPort = strconv.Itoa(int(detail.ExternalPort))
                    } else {
                            if *detail.Port != 0 {
                                    brokerPort = strconv.Itoa(int(*detail.Port))
                            } else {
                                    brokerPort = "43191"
                            }
                    }

                    if detail.ExternalHost != nil && *detail.ExternalHost != "" {
                            brokerHost = *detail.ExternalHost
                    } else {
                            brokerHost = *detail.IP
                    }

                    if brokerHost == "trap.noit.circonus.net" && brokerPort != "443" {
                            brokerPort = "443"
                    }

                    retries := 5
                    for attempt := 1; attempt <= retries; attempt++ {
                            // broker must be reachable and respond within designated time
                            conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", brokerHost, brokerPort), cm.brokerMaxResponseTime)
                            if err == nil {
                                    conn.Close()
                                    valid = true
                                    break
                            }

                            cm.Log.Printf("[WARN] Broker '%s' unable to connect, %v. Retrying in 2 seconds, attempt %d of %d.", broker.Name, err, attempt, retries)
                            time.Sleep(2 * time.Second)
                    }

                    if valid {
                            if cm.Debug {
                                    cm.Log.Printf("[DEBUG] Broker '%s' is valid\n", broker.Name)
                            }
                            break
                    }
            }
            return valid
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package checkmgr

    import (
            "crypto/x509"
            "encoding/json"
            "errors"
            "fmt"
    )

    // Default Circonus CA certificate
    var circonusCA = []byte(`-----BEGIN CERTIFICATE-----
    MIID4zCCA0ygAwIBAgIJAMelf8skwVWPMA0GCSqGSIb3DQEBBQUAMIGoMQswCQYD
    VQQGEwJVUzERMA8GA1UECBMITWFyeWxhbmQxETAPBgNVBAcTCENvbHVtYmlhMRcw
    FQYDVQQKEw5DaXJjb251cywgSW5jLjERMA8GA1UECxMIQ2lyY29udXMxJzAlBgNV
    BAMTHkNpcmNvbnVzIENlcnRpZmljYXRlIEF1dGhvcml0eTEeMBwGCSqGSIb3DQEJ
    ARYPY2FAY2lyY29udXMubmV0MB4XDTA5MTIyMzE5MTcwNloXDTE5MTIyMTE5MTcw
    NlowgagxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDERMA8GA1UEBxMI
    Q29sdW1iaWExFzAVBgNVBAoTDkNpcmNvbnVzLCBJbmMuMREwDwYDVQQLEwhDaXJj
    b251czEnMCUGA1UEAxMeQ2lyY29udXMgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR4w
    HAYJKoZIhvcNAQkBFg9jYUBjaXJjb251cy5uZXQwgZ8wDQYJKoZIhvcNAQEBBQAD
    gY0AMIGJAoGBAKz2X0/0vJJ4ad1roehFyxUXHdkjJA9msEKwT2ojummdUB3kK5z6
    PDzDL9/c65eFYWqrQWVWZSLQK1D+v9xJThCe93v6QkSJa7GZkCq9dxClXVtBmZH3
    hNIZZKVC6JMA9dpRjBmlFgNuIdN7q5aJsv8VZHH+QrAyr9aQmhDJAmk1AgMBAAGj
    ggERMIIBDTAdBgNVHQ4EFgQUyNTsgZHSkhhDJ5i+6IFlPzKYxsUwgd0GA1UdIwSB
    1TCB0oAUyNTsgZHSkhhDJ5i+6IFlPzKYxsWhga6kgaswgagxCzAJBgNVBAYTAlVT
    MREwDwYDVQQIEwhNYXJ5bGFuZDERMA8GA1UEBxMIQ29sdW1iaWExFzAVBgNVBAoT
    DkNpcmNvbnVzLCBJbmMuMREwDwYDVQQLEwhDaXJjb251czEnMCUGA1UEAxMeQ2ly
    Y29udXMgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9jYUBj
    aXJjb251cy5uZXSCCQDHpX/LJMFVjzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB
    BQUAA4GBAAHBtl15BwbSyq0dMEBpEdQYhHianU/rvOMe57digBmox7ZkPEbB/baE
    sYJysziA2raOtRxVRtcxuZSMij2RiJDsLxzIp1H60Xhr8lmf7qF6Y+sZl7V36KZb
    n2ezaOoRtsQl9dhqEMe8zgL76p9YZ5E69Al0mgiifTteyNjjMuIW
    -----END CERTIFICATE-----`)

    // CACert contains cert returned from Circonus API
    type CACert struct {
            Contents string `json:"contents"`
    }

    // loadCACert loads the CA cert for the broker designated by the submission url
    func (cm *CheckManager) loadCACert() error {
            if cm.certPool != nil {
                    return nil
            }

            cm.certPool = x509.NewCertPool()

            var cert []byte
            var err error

            if cm.enabled {
                    // only attempt to retrieve broker CA cert if
                    // the check is being managed.
                    cert, err = cm.fetchCert()
                    if err != nil {
                            return err
                    }
            }

            if cert == nil {
                    cert = circonusCA
            }

            cm.certPool.AppendCertsFromPEM(cert)

            return nil
    }

    // fetchCert fetches CA certificate using Circonus API
    func (cm *CheckManager) fetchCert() ([]byte, error) {
            if !cm.enabled {
                    return nil, errors.New("check manager is not enabled")
            }

            response, err := cm.apih.Get("/pki/ca.crt")
            if err != nil {
                    return nil, err
            }

            cadata := new(CACert)
            if err := json.Unmarshal(response, cadata); err != nil {
                    return nil, err
            }

            if cadata.Contents == "" {
                    return nil, fmt.Errorf("[ERROR] Unable to find ca cert %+v", cadata)
            }

            return []byte(cadata.Contents), nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package checkmgr

    import (
            "crypto/rand"
            "crypto/sha256"
            "encoding/hex"
            "errors"
            "fmt"
            "net/url"
            "strconv"
            "strings"
            "time"

            "github.com/circonus-labs/circonus-gometrics/api"
            "github.com/circonus-labs/circonus-gometrics/api/config"
    )

    // UpdateCheck determines if the check needs to be updated (new metrics, tags, etc.)
    func (cm *CheckManager) UpdateCheck(newMetrics map[string]*api.CheckBundleMetric) {
            // only if check manager is enabled
            if !cm.enabled {
                    return
            }

            // only if checkBundle has been populated
            if cm.checkBundle == nil {
                    return
            }

            // only if there is *something* to update
            if !cm.forceCheckUpdate && len(newMetrics) == 0 && len(cm.metricTags) == 0 {
                    return
            }

            // refresh check bundle (in case there were changes made by other apps or in UI)
            cid := cm.checkBundle.CID
            checkBundle, err := cm.apih.FetchCheckBundle(api.CIDType(&cid))
            if err != nil {
                    cm.Log.Printf("[ERROR] unable to fetch up-to-date check bundle %v", err)
                    return
            }
            cm.cbmu.Lock()
            cm.checkBundle = checkBundle
            cm.cbmu.Unlock()

            // check metric_limit and see if it’s 0, if so, don't even bother to try to update the check.

            cm.addNewMetrics(newMetrics)

            if len(cm.metricTags) > 0 {
                    // note: if a tag has been added (queued) for a metric which never gets sent
                    //       the tags will be discarded. (setting tags does not *create* metrics.)
                    for metricName, metricTags := range cm.metricTags {
                            for metricIdx, metric := range cm.checkBundle.Metrics {
                                    if metric.Name == metricName {
                                            cm.checkBundle.Metrics[metricIdx].Tags = metricTags
                                            break
                                    }
                            }
                            cm.mtmu.Lock()
                            delete(cm.metricTags, metricName)
                            cm.mtmu.Unlock()
                    }
                    cm.forceCheckUpdate = true
            }

            if cm.forceCheckUpdate {
                    newCheckBundle, err := cm.apih.UpdateCheckBundle(cm.checkBundle)
                    if err != nil {
                            cm.Log.Printf("[ERROR] updating check bundle %v", err)
                            return
                    }

                    cm.forceCheckUpdate = false
                    cm.cbmu.Lock()
                    cm.checkBundle = newCheckBundle
                    cm.cbmu.Unlock()
                    cm.inventoryMetrics()
            }

    }

    // Initialize CirconusMetrics instance. Attempt to find a check otherwise create one.
    // use cases:
    //
    // check [bundle] by submission url
    // check [bundle] by *check* id (note, not check_bundle id)
    // check [bundle] by search
    // create check [bundle]
    func (cm *CheckManager) initializeTrapURL() error {
            if cm.trapURL != "" {
                    return nil
            }

            cm.trapmu.Lock()
            defer cm.trapmu.Unlock()

            // special case short-circuit: just send to a url, no check management
            // up to user to ensure that if url is https that it will work (e.g. not self-signed)
            if cm.checkSubmissionURL != "" {
                    if !cm.enabled {
                            cm.trapURL = cm.checkSubmissionURL
                            cm.trapLastUpdate = time.Now()
                            return nil
                    }
            }

            if !cm.enabled {
                    return errors.New("unable to initialize trap, check manager is disabled")
            }

            var err error
            var check *api.Check
            var checkBundle *api.CheckBundle
            var broker *api.Broker

            if cm.checkSubmissionURL != "" {
                    check, err = cm.fetchCheckBySubmissionURL(cm.checkSubmissionURL)
                    if err != nil {
                            return err
                    }
                    if !check.Active {
                            return fmt.Errorf("[ERROR] Check ID %v is not active", check.CID)
                    }
                    // extract check id from check object returned from looking up using submission url
                    // set m.CheckId to the id
                    // set m.SubmissionUrl to "" to prevent trying to search on it going forward
                    // use case: if the broker is changed in the UI metrics would stop flowing
                    // unless the new submission url can be fetched with the API (which is no
                    // longer possible using the original submission url)
                    var id int
                    id, err = strconv.Atoi(strings.Replace(check.CID, "/check/", "", -1))
                    if err == nil {
                            cm.checkID = api.IDType(id)
                            cm.checkSubmissionURL = ""
                    } else {
                            cm.Log.Printf(
                                    "[WARN] SubmissionUrl check to Check ID: unable to convert %s to int %q\n",
                                    check.CID, err)
                    }
            } else if cm.checkID > 0 {
                    cid := fmt.Sprintf("/check/%d", cm.checkID)
                    check, err = cm.apih.FetchCheck(api.CIDType(&cid))
                    if err != nil {
                            return err
                    }
                    if !check.Active {
                            return fmt.Errorf("[ERROR] Check ID %v is not active", check.CID)
                    }
            } else {
                    if checkBundle == nil {
                            // old search (instanceid as check.target)
                            searchCriteria := fmt.Sprintf(
                                    "(active:1)(type:\"%s\")(host:\"%s\")(tags:%s)", cm.checkType, cm.checkTarget, strings.Join(cm.checkSearchTag, ","))
                            checkBundle, err = cm.checkBundleSearch(searchCriteria, map[string][]string{})
                            if err != nil {
                                    return err
                            }
                    }

                    if checkBundle == nil {
                            // new search (check.target != instanceid, instanceid encoded in notes field)
                            searchCriteria := fmt.Sprintf(
                                    "(active:1)(type:\"%s\")(tags:%s)", cm.checkType, strings.Join(cm.checkSearchTag, ","))
                            filterCriteria := map[string][]string{"f_notes": []string{*cm.getNotes()}}
                            checkBundle, err = cm.checkBundleSearch(searchCriteria, filterCriteria)
                            if err != nil {
                                    return err
                            }
                    }

                    if checkBundle == nil {
                            // err==nil && checkBundle==nil is "no check bundles matched"
                            // an error *should* be returned for any other invalid scenario
                            checkBundle, broker, err = cm.createNewCheck()
                            if err != nil {
                                    return err
                            }
                    }
            }

            if checkBundle == nil {
                    if check != nil {
                            cid := check.CheckBundleCID
                            checkBundle, err = cm.apih.FetchCheckBundle(api.CIDType(&cid))
                            if err != nil {
                                    return err
                            }
                    } else {
                            return fmt.Errorf("[ERROR] Unable to retrieve, find, or create check")
                    }
            }

            if broker == nil {
                    cid := checkBundle.Brokers[0]
                    broker, err = cm.apih.FetchBroker(api.CIDType(&cid))
                    if err != nil {
                            return err
                    }
            }

            // retain to facilitate metric management (adding new metrics specifically)
            cm.checkBundle = checkBundle
            cm.inventoryMetrics()

            // determine the trap url to which metrics should be PUT
            if checkBundle.Type == "httptrap" {
                    if turl, found := checkBundle.Config[config.SubmissionURL]; found {
                            cm.trapURL = api.URLType(turl)
                    } else {
                            if cm.Debug {
                                    cm.Log.Printf("Missing config.%s %+v", config.SubmissionURL, checkBundle)
                            }
                            return fmt.Errorf("[ERROR] Unable to use check, no %s in config", config.SubmissionURL)
                    }
            } else {
                    // build a submission_url for non-httptrap checks out of mtev_reverse url
                    if len(checkBundle.ReverseConnectURLs) == 0 {
                            return fmt.Errorf("%s is not an HTTPTRAP check and no reverse connection urls found", checkBundle.Checks[0])
                    }
                    mtevURL := checkBundle.ReverseConnectURLs[0]
                    mtevURL = strings.Replace(mtevURL, "mtev_reverse", "https", 1)
                    mtevURL = strings.Replace(mtevURL, "check", "module/httptrap", 1)
                    if rs, found := checkBundle.Config[config.ReverseSecretKey]; found {
                            cm.trapURL = api.URLType(fmt.Sprintf("%s/%s", mtevURL, rs))
                    } else {
                            if cm.Debug {
                                    cm.Log.Printf("Missing config.%s %+v", config.ReverseSecretKey, checkBundle)
                            }
                            return fmt.Errorf("[ERROR] Unable to use check, no %s in config", config.ReverseSecretKey)
                    }
            }

            // used when sending as "ServerName" get around certs not having IP SANS
            // (cert created with server name as CN but IP used in trap url)
            cn, err := cm.getBrokerCN(broker, cm.trapURL)
            if err != nil {
                    return err
            }
            cm.trapCN = BrokerCNType(cn)

            if cm.enabled {
                    u, err := url.Parse(string(cm.trapURL))
                    if err != nil {
                            return err
                    }
                    if u.Scheme == "https" {
                            if err := cm.loadCACert(); err != nil {
                                    return err
                            }
                    }
            }

            cm.trapLastUpdate = time.Now()

            return nil
    }

    // Search for a check bundle given a predetermined set of criteria
    func (cm *CheckManager) checkBundleSearch(criteria string, filter map[string][]string) (*api.CheckBundle, error) {
            search := api.SearchQueryType(criteria)
            checkBundles, err := cm.apih.SearchCheckBundles(&search, &filter)
            if err != nil {
                    return nil, err
            }

            if len(*checkBundles) == 0 {
                    return nil, nil // trigger creation of a new check
            }

            numActive := 0
            checkID := -1

            for idx, check := range *checkBundles {
                    if check.Status == statusActive {
                            numActive++
                            checkID = idx
                    }
            }

            if numActive > 1 {
                    return nil, fmt.Errorf("[ERROR] multiple check bundles match criteria %s", criteria)
            }

            bundle := (*checkBundles)[checkID]

            return &bundle, nil
    }

    // Create a new check to receive metrics
    func (cm *CheckManager) createNewCheck() (*api.CheckBundle, *api.Broker, error) {
            checkSecret := string(cm.checkSecret)
            if checkSecret == "" {
                    secret, err := cm.makeSecret()
                    if err != nil {
                            secret = "myS3cr3t"
                    }
                    checkSecret = secret
            }

            broker, err := cm.getBroker()
            if err != nil {
                    return nil, nil, err
            }

            chkcfg := &api.CheckBundle{
                    Brokers:     []string{broker.CID},
                    Config:      make(map[config.Key]string),
                    DisplayName: string(cm.checkDisplayName),
                    Metrics:     []api.CheckBundleMetric{},
                    MetricLimit: config.DefaultCheckBundleMetricLimit,
                    Notes:       cm.getNotes(),
                    Period:      60,
                    Status:      statusActive,
                    Tags:        append(cm.checkSearchTag, cm.checkTags...),
                    Target:      string(cm.checkTarget),
                    Timeout:     10,
                    Type:        string(cm.checkType),
            }

            if len(cm.customConfigFields) > 0 {
                    for fld, val := range cm.customConfigFields {
                            chkcfg.Config[config.Key(fld)] = val
                    }
            }

            //
            // use the default config settings if these are NOT set by user configuration
            //
            if val, ok := chkcfg.Config[config.AsyncMetrics]; !ok || val == "" {
                    chkcfg.Config[config.AsyncMetrics] = "true"
            }

            if val, ok := chkcfg.Config[config.Secret]; !ok || val == "" {
                    chkcfg.Config[config.Secret] = checkSecret
            }

            checkBundle, err := cm.apih.CreateCheckBundle(chkcfg)
            if err != nil {
                    return nil, nil, err
            }

            return checkBundle, broker, nil
    }

    // Create a dynamic secret to use with a new check
    func (cm *CheckManager) makeSecret() (string, error) {
            hash := sha256.New()
            x := make([]byte, 2048)
            if _, err := rand.Read(x); err != nil {
                    return "", err
            }
            hash.Write(x)
            return hex.EncodeToString(hash.Sum(nil))[0:16], nil
    }

    func (cm *CheckManager) getNotes() *string {
            notes := fmt.Sprintf("cgm_instanceid|%s", cm.checkInstanceID)
            return &notes
    }

    // FetchCheckBySubmissionURL fetch a check configuration by submission_url
    func (cm *CheckManager) fetchCheckBySubmissionURL(submissionURL api.URLType) (*api.Check, error) {
            if string(submissionURL) == "" {
                    return nil, errors.New("[ERROR] Invalid submission URL (blank)")
            }

            u, err := url.Parse(string(submissionURL))
            if err != nil {
                    return nil, err
            }

            // valid trap url: scheme://host[:port]/module/httptrap/UUID/secret

            // does it smell like a valid trap url path
            if !strings.Contains(u.Path, "/module/httptrap/") {
                    return nil, fmt.Errorf("[ERROR] Invalid submission URL '%s', unrecognized path", submissionURL)
            }

            // extract uuid
            pathParts := strings.Split(strings.Replace(u.Path, "/module/httptrap/", "", 1), "/")
            if len(pathParts) != 2 {
                    return nil, fmt.Errorf("[ERROR] Invalid submission URL '%s', UUID not where expected", submissionURL)
            }
            uuid := pathParts[0]

            filter := api.SearchFilterType{"f__check_uuid": []string{uuid}}

            checks, err := cm.apih.SearchChecks(nil, &filter)
            if err != nil {
                    return nil, err
            }

            if len(*checks) == 0 {
                    return nil, fmt.Errorf("[ERROR] No checks found with UUID %s", uuid)
            }

            numActive := 0
            checkID := -1

            for idx, check := range *checks {
                    if check.Active {
                            numActive++
                            checkID = idx
                    }
            }

            if numActive > 1 {
                    return nil, fmt.Errorf("[ERROR] Multiple checks with same UUID %s", uuid)
            }

            check := (*checks)[checkID]

            return &check, nil

    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Package checkmgr provides a check management interace to circonus-gometrics
    package checkmgr

    import (
            "crypto/tls"
            "crypto/x509"
            "errors"
            "fmt"
            "io/ioutil"
            "log"
            "net/url"
            "os"
            "path"
            "strconv"
            "strings"
            "sync"
            "time"

            "github.com/circonus-labs/circonus-gometrics/api"
    )

    // Check management offers:
    //
    // Create a check if one cannot be found matching specific criteria
    // Manage metrics in the supplied check (enabling new metrics as they are submitted)
    //
    // To disable check management, leave Config.Api.Token.Key blank
    //
    // use cases:
    // configure without api token - check management disabled
    //  - configuration parameters other than Check.SubmissionUrl, Debug and Log are ignored
    //  - note: SubmissionUrl is **required** in this case as there is no way to derive w/o api
    // configure with api token - check management enabled
    //  - all otehr configuration parameters affect how the trap url is obtained
    //    1. provided (Check.SubmissionUrl)
    //    2. via check lookup (CheckConfig.Id)
    //    3. via a search using CheckConfig.InstanceId + CheckConfig.SearchTag
    //    4. a new check is created

    const (
            defaultCheckType             = "httptrap"
            defaultTrapMaxURLAge         = "60s"   // 60 seconds
            defaultBrokerMaxResponseTime = "500ms" // 500 milliseconds
            defaultForceMetricActivation = "false"
            statusActive                 = "active"
    )

    // CheckConfig options for check
    type CheckConfig struct {
            // a specific submission url
            SubmissionURL string
            // a specific check id (not check bundle id)
            ID string
            // unique instance id string
            // used to search for a check to use
            // used as check.target when creating a check
            InstanceID string
            // explicitly set check.target (default: instance id)
            TargetHost string
            // a custom display name for the check (as viewed in UI Checks)
            // default: instance id
            DisplayName string
            // unique check searching tag (or tags)
            // used to search for a check to use (combined with instanceid)
            // used as a regular tag when creating a check
            SearchTag string
            // httptrap check secret (for creating a check)
            Secret string
            // additional tags to add to a check (when creating a check)
            // these tags will not be added to an existing check
            Tags string
            // max amount of time to to hold on to a submission url
            // when a given submission fails (due to retries) if the
            // time the url was last updated is > than this, the trap
            // url will be refreshed (e.g. if the broker is changed
            // in the UI) **only relevant when check management is enabled**
            // e.g. 5m, 30m, 1h, etc.
            MaxURLAge string
            // force metric activation - if a metric has been disabled via the UI
            // the default behavior is to *not* re-activate the metric; this setting
            // overrides the behavior and will re-activate the metric when it is
            // encountered. "(true|false)", default "false"
            ForceMetricActivation string
            // Type of check to use (default: httptrap)
            Type string
            // Custom check config fields (default: none)
            CustomConfigFields map[string]string
    }

    // BrokerConfig options for broker
    type BrokerConfig struct {
            // a specific broker id (numeric portion of cid)
            ID string
            // one or more tags used to select 1-n brokers from which to select
            // when creating a new check (e.g. datacenter:abc or loc:dfw,dc:abc)
            SelectTag string
            // for a broker to be considered viable it must respond to a
            // connection attempt within this amount of time e.g. 200ms, 2s, 1m
            MaxResponseTime string
            // TLS configuration to use when communicating wtih broker
            TLSConfig *tls.Config
    }

    // Config options
    type Config struct {
            Log   *log.Logger
            Debug bool

            // Circonus API config
            API api.Config
            // Check specific configuration options
            Check CheckConfig
            // Broker specific configuration options
            Broker BrokerConfig
    }

    // CheckTypeType check type
    type CheckTypeType string

    // CheckInstanceIDType check instance id
    type CheckInstanceIDType string

    // CheckTargetType check target/host
    type CheckTargetType string

    // CheckSecretType check secret
    type CheckSecretType string

    // CheckTagsType check tags
    type CheckTagsType string

    // CheckDisplayNameType check display name
    type CheckDisplayNameType string

    // BrokerCNType broker common name
    type BrokerCNType string

    // CheckManager settings
    type CheckManager struct {
            enabled bool
            Log     *log.Logger
            Debug   bool
            apih    *api.API

            initialized   bool
            initializedmu sync.RWMutex

            // check
            checkType             CheckTypeType
            checkID               api.IDType
            checkInstanceID       CheckInstanceIDType
            checkTarget           CheckTargetType
            checkSearchTag        api.TagType
            checkSecret           CheckSecretType
            checkTags             api.TagType
            customConfigFields    map[string]string
            checkSubmissionURL    api.URLType
            checkDisplayName      CheckDisplayNameType
            forceMetricActivation bool
            forceCheckUpdate      bool

            // metric tags
            metricTags map[string][]string
            mtmu       sync.Mutex

            // broker
            brokerID              api.IDType
            brokerSelectTag       api.TagType
            brokerMaxResponseTime time.Duration
            brokerTLS             *tls.Config

            // state
            checkBundle        *api.CheckBundle
            cbmu               sync.Mutex
            availableMetrics   map[string]bool
            availableMetricsmu sync.Mutex
            trapURL            api.URLType
            trapCN             BrokerCNType
            trapLastUpdate     time.Time
            trapMaxURLAge      time.Duration
            trapmu             sync.Mutex
            certPool           *x509.CertPool
    }

    // Trap config
    type Trap struct {
            URL *url.URL
            TLS *tls.Config
    }

    // NewCheckManager returns a new check manager
    func NewCheckManager(cfg *Config) (*CheckManager, error) {
            return New(cfg)
    }

    // New returns a new check manager
    func New(cfg *Config) (*CheckManager, error) {

            if cfg == nil {
                    return nil, errors.New("invalid Check Manager configuration (nil)")
            }

            cm := &CheckManager{enabled: true, initialized: false}

            // Setup logging for check manager
            cm.Debug = cfg.Debug
            cm.Log = cfg.Log
            if cm.Debug && cm.Log == nil {
                    cm.Log = log.New(os.Stderr, "", log.LstdFlags)
            }
            if cm.Log == nil {
                    cm.Log = log.New(ioutil.Discard, "", log.LstdFlags)
            }

            if cfg.Check.SubmissionURL != "" {
                    cm.checkSubmissionURL = api.URLType(cfg.Check.SubmissionURL)
            }

            // Blank API Token *disables* check management
            if cfg.API.TokenKey == "" {
                    cm.enabled = false
            }

            if !cm.enabled && cm.checkSubmissionURL == "" {
                    return nil, errors.New("invalid check manager configuration (no API token AND no submission url)")
            }

            if cm.enabled {
                    // initialize api handle
                    cfg.API.Debug = cm.Debug
                    cfg.API.Log = cm.Log
                    apih, err := api.New(&cfg.API)
                    if err != nil {
                            return nil, err
                    }
                    cm.apih = apih
            }

            // initialize check related data
            if cfg.Check.Type != "" {
                    cm.checkType = CheckTypeType(cfg.Check.Type)
            } else {
                    cm.checkType = defaultCheckType
            }

            idSetting := "0"
            if cfg.Check.ID != "" {
                    idSetting = cfg.Check.ID
            }
            id, err := strconv.Atoi(idSetting)
            if err != nil {
                    return nil, err
            }
            cm.checkID = api.IDType(id)

            cm.checkInstanceID = CheckInstanceIDType(cfg.Check.InstanceID)
            cm.checkTarget = CheckTargetType(cfg.Check.TargetHost)
            cm.checkDisplayName = CheckDisplayNameType(cfg.Check.DisplayName)
            cm.checkSecret = CheckSecretType(cfg.Check.Secret)

            fma := defaultForceMetricActivation
            if cfg.Check.ForceMetricActivation != "" {
                    fma = cfg.Check.ForceMetricActivation
            }
            fm, err := strconv.ParseBool(fma)
            if err != nil {
                    return nil, err
            }
            cm.forceMetricActivation = fm

            _, an := path.Split(os.Args[0])
            hn, err := os.Hostname()
            if err != nil {
                    hn = "unknown"
            }
            if cm.checkInstanceID == "" {
                    cm.checkInstanceID = CheckInstanceIDType(fmt.Sprintf("%s:%s", hn, an))
            }
            if cm.checkDisplayName == "" {
                    cm.checkDisplayName = CheckDisplayNameType(cm.checkInstanceID)
            }
            if cm.checkTarget == "" {
                    cm.checkTarget = CheckTargetType(cm.checkInstanceID)
            }

            if cfg.Check.SearchTag == "" {
                    cm.checkSearchTag = []string{fmt.Sprintf("service:%s", an)}
            } else {
                    cm.checkSearchTag = strings.Split(strings.Replace(cfg.Check.SearchTag, " ", "", -1), ",")
            }

            if cfg.Check.Tags != "" {
                    cm.checkTags = strings.Split(strings.Replace(cfg.Check.Tags, " ", "", -1), ",")
            }

            cm.customConfigFields = make(map[string]string)
            if len(cfg.Check.CustomConfigFields) > 0 {
                    for fld, val := range cfg.Check.CustomConfigFields {
                            cm.customConfigFields[fld] = val
                    }
            }

            dur := cfg.Check.MaxURLAge
            if dur == "" {
                    dur = defaultTrapMaxURLAge
            }
            maxDur, err := time.ParseDuration(dur)
            if err != nil {
                    return nil, err
            }
            cm.trapMaxURLAge = maxDur

            // setup broker
            idSetting = "0"
            if cfg.Broker.ID != "" {
                    idSetting = cfg.Broker.ID
            }
            id, err = strconv.Atoi(idSetting)
            if err != nil {
                    return nil, err
            }
            cm.brokerID = api.IDType(id)

            if cfg.Broker.SelectTag != "" {
                    cm.brokerSelectTag = strings.Split(strings.Replace(cfg.Broker.SelectTag, " ", "", -1), ",")
            }

            dur = cfg.Broker.MaxResponseTime
            if dur == "" {
                    dur = defaultBrokerMaxResponseTime
            }
            maxDur, err = time.ParseDuration(dur)
            if err != nil {
                    return nil, err
            }
            cm.brokerMaxResponseTime = maxDur

            // add user specified tls config for broker if provided
            cm.brokerTLS = cfg.Broker.TLSConfig

            // metrics
            cm.availableMetrics = make(map[string]bool)
            cm.metricTags = make(map[string][]string)

            return cm, nil
    }

    // Initialize for sending metrics
    func (cm *CheckManager) Initialize() {

            // if not managing the check, quicker initialization
            if !cm.enabled {
                    err := cm.initializeTrapURL()
                    if err == nil {
                            cm.initializedmu.Lock()
                            cm.initialized = true
                            cm.initializedmu.Unlock()
                    } else {
                            cm.Log.Printf("[WARN] error initializing trap %s", err.Error())
                    }
                    return
            }

            // background initialization when we have to reach out to the api
            go func() {
                    cm.apih.EnableExponentialBackoff()
                    err := cm.initializeTrapURL()
                    if err == nil {
                            cm.initializedmu.Lock()
                            cm.initialized = true
                            cm.initializedmu.Unlock()
                    } else {
                            cm.Log.Printf("[WARN] error initializing trap %s", err.Error())
                    }
                    cm.apih.DisableExponentialBackoff()
            }()
    }

    // IsReady reflects if the check has been initialied and metrics can be sent to Circonus
    func (cm *CheckManager) IsReady() bool {
            cm.initializedmu.RLock()
            defer cm.initializedmu.RUnlock()
            return cm.initialized
    }

    // GetSubmissionURL returns submission url for circonus
    func (cm *CheckManager) GetSubmissionURL() (*Trap, error) {
            if cm.trapURL == "" {
                    return nil, fmt.Errorf("[ERROR] no submission url currently available")
                    // if err := cm.initializeTrapURL(); err != nil {
                    //         return nil, err
                    // }
            }

            trap := &Trap{}

            u, err := url.Parse(string(cm.trapURL))
            if err != nil {
                    return nil, err
            }

            trap.URL = u

            if u.Scheme != "https" {
                    return trap, nil
            }

            // preference user-supplied TLS configuration
            if cm.brokerTLS != nil {
                    trap.TLS = cm.brokerTLS
                    return trap, nil
            }

            if cm.certPool == nil {
                    if err := cm.loadCACert(); err != nil {
                            return nil, err
                    }
            }
            t := &tls.Config{
                    RootCAs: cm.certPool,
            }
            if cm.trapCN != "" {
                    t.ServerName = string(cm.trapCN)
            }
            trap.TLS = t

            return trap, nil
    }

    // ResetTrap URL, force request to the API for the submission URL and broker ca cert
    func (cm *CheckManager) ResetTrap() error {
            if cm.trapURL == "" {
                    return nil
            }

            cm.trapURL = ""
            cm.certPool = nil // force re-fetching CA cert (if custom TLS config not supplied)
            return cm.initializeTrapURL()
    }

    // RefreshTrap check when the last time the URL was reset, reset if needed
    func (cm *CheckManager) RefreshTrap() error {
            if cm.trapURL == "" {
                    return nil
            }

            if time.Since(cm.trapLastUpdate) >= cm.trapMaxURLAge {
                    return cm.ResetTrap()
            }

            return nil
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package checkmgr

    import (
            "github.com/circonus-labs/circonus-gometrics/api"
    )

    // IsMetricActive checks whether a given metric name is currently active(enabled)
    func (cm *CheckManager) IsMetricActive(name string) bool {
            cm.availableMetricsmu.Lock()
            defer cm.availableMetricsmu.Unlock()

            active, _ := cm.availableMetrics[name]
            return active
    }

    // ActivateMetric determines if a given metric should be activated
    func (cm *CheckManager) ActivateMetric(name string) bool {
            cm.availableMetricsmu.Lock()
            defer cm.availableMetricsmu.Unlock()

            active, exists := cm.availableMetrics[name]

            if !exists {
                    return true
            }

            if !active && cm.forceMetricActivation {
                    return true
            }

            return false
    }

    // AddMetricTags updates check bundle metrics with tags
    func (cm *CheckManager) AddMetricTags(metricName string, tags []string, appendTags bool) bool {
            tagsUpdated := false

            if appendTags && len(tags) == 0 {
                    return tagsUpdated
            }

            currentTags, exists := cm.metricTags[metricName]
            if !exists {
                    foundMetric := false

                    if cm.checkBundle != nil {
                            for _, metric := range cm.checkBundle.Metrics {
                                    if metric.Name == metricName {
                                            foundMetric = true
                                            currentTags = metric.Tags
                                            break
                                    }
                            }
                    }

                    if !foundMetric {
                            currentTags = []string{}
                    }
            }

            action := ""
            if appendTags {
                    numNewTags := countNewTags(currentTags, tags)
                    if numNewTags > 0 {
                            action = "Added"
                            currentTags = append(currentTags, tags...)
                            tagsUpdated = true
                    }
            } else {
                    if len(tags) != len(currentTags) {
                            action = "Set"
                            currentTags = tags
                            tagsUpdated = true
                    } else {
                            numNewTags := countNewTags(currentTags, tags)
                            if numNewTags > 0 {
                                    action = "Set"
                                    currentTags = tags
                                    tagsUpdated = true
                            }
                    }
            }

            if tagsUpdated {
                    cm.metricTags[metricName] = currentTags
            }

            if cm.Debug && action != "" {
                    cm.Log.Printf("[DEBUG] %s metric tag(s) %s %v\n", action, metricName, tags)
            }

            return tagsUpdated
    }

    // addNewMetrics updates a check bundle with new metrics
    func (cm *CheckManager) addNewMetrics(newMetrics map[string]*api.CheckBundleMetric) bool {
            updatedCheckBundle := false

            if cm.checkBundle == nil || len(newMetrics) == 0 {
                    return updatedCheckBundle
            }

            cm.cbmu.Lock()
            defer cm.cbmu.Unlock()

            numCurrMetrics := len(cm.checkBundle.Metrics)
            numNewMetrics := len(newMetrics)

            if numCurrMetrics+numNewMetrics >= cap(cm.checkBundle.Metrics) {
                    nm := make([]api.CheckBundleMetric, numCurrMetrics+numNewMetrics)
                    copy(nm, cm.checkBundle.Metrics)
                    cm.checkBundle.Metrics = nm
            }

            cm.checkBundle.Metrics = cm.checkBundle.Metrics[0 : numCurrMetrics+numNewMetrics]

            i := 0
            for _, metric := range newMetrics {
                    cm.checkBundle.Metrics[numCurrMetrics+i] = *metric
                    i++
                    updatedCheckBundle = true
            }

            if updatedCheckBundle {
                    cm.forceCheckUpdate = true
            }

            return updatedCheckBundle
    }

    // inventoryMetrics creates list of active metrics in check bundle
    func (cm *CheckManager) inventoryMetrics() {
            availableMetrics := make(map[string]bool)
            for _, metric := range cm.checkBundle.Metrics {
                    availableMetrics[metric.Name] = metric.Status == "active"
            }
            cm.availableMetricsmu.Lock()
            cm.availableMetrics = availableMetrics
            cm.availableMetricsmu.Unlock()
    }

    // countNewTags returns a count of new tags which do not exist in the current list of tags
    func countNewTags(currTags []string, newTags []string) int {
            if len(newTags) == 0 {
                    return 0
            }

            if len(currTags) == 0 {
                    return len(newTags)
            }

            newTagCount := 0

            for _, newTag := range newTags {
                    found := false
                    for _, currTag := range currTags {
                            if newTag == currTag {
                                    found = true
                                    break
                            }
                    }
                    if !found {
                            newTagCount++
                    }
            }

            return newTagCount
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    // Package circonusgometrics provides instrumentation for your applications in the form
    // of counters, gauges and histograms and allows you to publish them to
    // Circonus
    //
    // Counters
    //
    // A counter is a monotonically-increasing, unsigned, 64-bit integer used to
    // represent the number of times an event has occurred. By tracking the deltas
    // between measurements of a counter over intervals of time, an aggregation
    // layer can derive rates, acceleration, etc.
    //
    // Gauges
    //
    // A gauge returns instantaneous measurements of something using signed, 64-bit
    // integers. This value does not need to be monotonic.
    //
    // Histograms
    //
    // A histogram tracks the distribution of a stream of values (e.g. the number of
    // seconds it takes to handle requests).  Circonus can calculate complex
    // analytics on these.
    //
    // Reporting
    //
    // A period push to a Circonus httptrap is confgurable.
    package circonusgometrics

    import (
            "errors"
            "io/ioutil"
            "log"
            "os"
            "strconv"
            "sync"
            "time"

            "github.com/circonus-labs/circonus-gometrics/api"
            "github.com/circonus-labs/circonus-gometrics/checkmgr"
    )

    const (
            defaultFlushInterval = "10s" // 10 * time.Second
    )

    // Metric defines an individual metric
    type Metric struct {
            Type  string      `json:"_type"`
            Value interface{} `json:"_value"`
    }

    // Metrics holds host metrics
    type Metrics map[string]Metric

    // Config options for circonus-gometrics
    type Config struct {
            Log             *log.Logger
            Debug           bool
            ResetCounters   string // reset/delete counters on flush (default true)
            ResetGauges     string // reset/delete gauges on flush (default true)
            ResetHistograms string // reset/delete histograms on flush (default true)
            ResetText       string // reset/delete text on flush (default true)

            // API, Check and Broker configuration options
            CheckManager checkmgr.Config

            // how frequenly to submit metrics to Circonus, default 10 seconds.
            // Set to 0 to disable automatic flushes and call Flush manually.
            Interval string
    }

    // CirconusMetrics state
    type CirconusMetrics struct {
            Log   *log.Logger
            Debug bool

            resetCounters   bool
            resetGauges     bool
            resetHistograms bool
            resetText       bool
            flushInterval   time.Duration
            flushing        bool
            flushmu         sync.Mutex
            packagingmu     sync.Mutex
            check           *checkmgr.CheckManager

            counters map[string]uint64
            cm       sync.Mutex

            counterFuncs map[string]func() uint64
            cfm          sync.Mutex

            gauges map[string]string
            gm     sync.Mutex

            gaugeFuncs map[string]func() int64
            gfm        sync.Mutex

            histograms map[string]*Histogram
            hm         sync.Mutex

            text map[string]string
            tm   sync.Mutex

            textFuncs map[string]func() string
            tfm       sync.Mutex
    }

    // NewCirconusMetrics returns a CirconusMetrics instance
    func NewCirconusMetrics(cfg *Config) (*CirconusMetrics, error) {
            return New(cfg)
    }

    // New returns a CirconusMetrics instance
    func New(cfg *Config) (*CirconusMetrics, error) {

            if cfg == nil {
                    return nil, errors.New("invalid configuration (nil)")
            }

            cm := &CirconusMetrics{
                    counters:     make(map[string]uint64),
                    counterFuncs: make(map[string]func() uint64),
                    gauges:       make(map[string]string),
                    gaugeFuncs:   make(map[string]func() int64),
                    histograms:   make(map[string]*Histogram),
                    text:         make(map[string]string),
                    textFuncs:    make(map[string]func() string),
            }

            // Logging
            {
                    cm.Debug = cfg.Debug
                    cm.Log = cfg.Log

                    if cm.Debug && cm.Log == nil {
                            cm.Log = log.New(os.Stderr, "", log.LstdFlags)
                    }
                    if cm.Log == nil {
                            cm.Log = log.New(ioutil.Discard, "", log.LstdFlags)
                    }
            }

            // Flush Interval
            {
                    fi := defaultFlushInterval
                    if cfg.Interval != "" {
                            fi = cfg.Interval
                    }

                    dur, err := time.ParseDuration(fi)
                    if err != nil {
                            return nil, err
                    }
                    cm.flushInterval = dur
            }

            // metric resets

            cm.resetCounters = true
            if cfg.ResetCounters != "" {
                    setting, err := strconv.ParseBool(cfg.ResetCounters)
                    if err != nil {
                            return nil, err
                    }
                    cm.resetCounters = setting
            }

            cm.resetGauges = true
            if cfg.ResetGauges != "" {
                    setting, err := strconv.ParseBool(cfg.ResetGauges)
                    if err != nil {
                            return nil, err
                    }
                    cm.resetGauges = setting
            }

            cm.resetHistograms = true
            if cfg.ResetHistograms != "" {
                    setting, err := strconv.ParseBool(cfg.ResetHistograms)
                    if err != nil {
                            return nil, err
                    }
                    cm.resetHistograms = setting
            }

            cm.resetText = true
            if cfg.ResetText != "" {
                    setting, err := strconv.ParseBool(cfg.ResetText)
                    if err != nil {
                            return nil, err
                    }
                    cm.resetText = setting
            }

            // check manager
            {
                    cfg.CheckManager.Debug = cm.Debug
                    cfg.CheckManager.Log = cm.Log

                    check, err := checkmgr.New(&cfg.CheckManager)
                    if err != nil {
                            return nil, err
                    }
                    cm.check = check
            }

            // start background initialization
            cm.check.Initialize()

            // if automatic flush is enabled, start it.
            // note: submit will jettison metrics until initialization has completed.
            if cm.flushInterval > time.Duration(0) {
                    go func() {
                            for range time.NewTicker(cm.flushInterval).C {
                                    cm.Flush()
                            }
                    }()
            }

            return cm, nil
    }

    // Start deprecated NOP, automatic flush is started in New if flush interval > 0.
    func (m *CirconusMetrics) Start() {
            return
    }

    // Ready returns true or false indicating if the check is ready to accept metrics
    func (m *CirconusMetrics) Ready() bool {
            return m.check.IsReady()
    }

    func (m *CirconusMetrics) packageMetrics() (map[string]*api.CheckBundleMetric, Metrics) {

            m.packagingmu.Lock()
            defer m.packagingmu.Unlock()

            if m.Debug {
                    m.Log.Println("[DEBUG] Packaging metrics")
            }

            counters, gauges, histograms, text := m.snapshot()
            newMetrics := make(map[string]*api.CheckBundleMetric)
            output := make(Metrics, len(counters)+len(gauges)+len(histograms)+len(text))
            for name, value := range counters {
                    send := m.check.IsMetricActive(name)
                    if !send && m.check.ActivateMetric(name) {
                            send = true
                            newMetrics[name] = &api.CheckBundleMetric{
                                    Name:   name,
                                    Type:   "numeric",
                                    Status: "active",
                            }
                    }
                    if send {
                            output[name] = Metric{Type: "L", Value: value}
                    }
            }

            for name, value := range gauges {
                    send := m.check.IsMetricActive(name)
                    if !send && m.check.ActivateMetric(name) {
                            send = true
                            newMetrics[name] = &api.CheckBundleMetric{
                                    Name:   name,
                                    Type:   "numeric",
                                    Status: "active",
                            }
                    }
                    if send {
                            output[name] = Metric{Type: "n", Value: value}
                    }
            }

            for name, value := range histograms {
                    send := m.check.IsMetricActive(name)
                    if !send && m.check.ActivateMetric(name) {
                            send = true
                            newMetrics[name] = &api.CheckBundleMetric{
                                    Name:   name,
                                    Type:   "histogram",
                                    Status: "active",
                            }
                    }
                    if send {
                            output[name] = Metric{Type: "n", Value: value.DecStrings()}
                    }
            }

            for name, value := range text {
                    send := m.check.IsMetricActive(name)
                    if !send && m.check.ActivateMetric(name) {
                            send = true
                            newMetrics[name] = &api.CheckBundleMetric{
                                    Name:   name,
                                    Type:   "text",
                                    Status: "active",
                            }
                    }
                    if send {
                            output[name] = Metric{Type: "s", Value: value}
                    }
            }

            return newMetrics, output
    }

    // FlushMetrics flushes current metrics to a structure and returns it (does NOT send to Circonus)
    func (m *CirconusMetrics) FlushMetrics() *Metrics {
            m.flushmu.Lock()
            if m.flushing {
                    m.flushmu.Unlock()
                    return &Metrics{}
            }

            m.flushing = true
            m.flushmu.Unlock()

            _, output := m.packageMetrics()

            m.flushmu.Lock()
            m.flushing = false
            m.flushmu.Unlock()

            return &output
    }

    // Flush metrics kicks off the process of sending metrics to Circonus
    func (m *CirconusMetrics) Flush() {
            m.flushmu.Lock()
            if m.flushing {
                    m.flushmu.Unlock()
                    return
            }

            m.flushing = true
            m.flushmu.Unlock()

            newMetrics, output := m.packageMetrics()

            if len(output) > 0 {
                    m.submit(output, newMetrics)
            } else {
                    if m.Debug {
                            m.Log.Println("[DEBUG] No metrics to send, skipping")
                    }
            }

            m.flushmu.Lock()
            m.flushing = false
            m.flushmu.Unlock()
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package circonusgometrics

    import "fmt"

    // A Counter is a monotonically increasing unsigned integer.
    //
    // Use a counter to derive rates (e.g., record total number of requests, derive
    // requests per second).

    // Increment counter by 1
    func (m *CirconusMetrics) Increment(metric string) {
            m.Add(metric, 1)
    }

    // IncrementByValue updates counter by supplied value
    func (m *CirconusMetrics) IncrementByValue(metric string, val uint64) {
            m.Add(metric, val)
    }

    // Set a counter to specific value
    func (m *CirconusMetrics) Set(metric string, val uint64) {
            m.cm.Lock()
            defer m.cm.Unlock()
            m.counters[metric] = val
    }

    // Add updates counter by supplied value
    func (m *CirconusMetrics) Add(metric string, val uint64) {
            m.cm.Lock()
            defer m.cm.Unlock()
            m.counters[metric] += val
    }

    // RemoveCounter removes the named counter
    func (m *CirconusMetrics) RemoveCounter(metric string) {
            m.cm.Lock()
            defer m.cm.Unlock()
            delete(m.counters, metric)
    }

    // GetCounterTest returns the current value for a counter. (note: it is a function specifically for "testing", disable automatic submission during testing.)
    func (m *CirconusMetrics) GetCounterTest(metric string) (uint64, error) {
            m.cm.Lock()
            defer m.cm.Unlock()

            if val, ok := m.counters[metric]; ok {
                    return val, nil
            }

            return 0, fmt.Errorf("Counter metric '%s' not found", metric)

    }

    // SetCounterFunc set counter to a function [called at flush interval]
    func (m *CirconusMetrics) SetCounterFunc(metric string, fn func() uint64) {
            m.cfm.Lock()
            defer m.cfm.Unlock()
            m.counterFuncs[metric] = fn
    }

    // RemoveCounterFunc removes the named counter function
    func (m *CirconusMetrics) RemoveCounterFunc(metric string) {
            m.cfm.Lock()
            defer m.cfm.Unlock()
            delete(m.counterFuncs, metric)
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package circonusgometrics

    // A Gauge is an instantaneous measurement of a value.
    //
    // Use a gauge to track metrics which increase and decrease (e.g., amount of
    // free memory).

    import (
            "fmt"
    )

    // Gauge sets a gauge to a value
    func (m *CirconusMetrics) Gauge(metric string, val interface{}) {
            m.SetGauge(metric, val)
    }

    // SetGauge sets a gauge to a value
    func (m *CirconusMetrics) SetGauge(metric string, val interface{}) {
            m.gm.Lock()
            defer m.gm.Unlock()
            m.gauges[metric] = m.gaugeValString(val)
    }

    // RemoveGauge removes a gauge
    func (m *CirconusMetrics) RemoveGauge(metric string) {
            m.gm.Lock()
            defer m.gm.Unlock()
            delete(m.gauges, metric)
    }

    // GetGaugeTest returns the current value for a gauge. (note: it is a function specifically for "testing", disable automatic submission during testing.)
    func (m *CirconusMetrics) GetGaugeTest(metric string) (string, error) {
            m.gm.Lock()
            defer m.gm.Unlock()

            if val, ok := m.gauges[metric]; ok {
                    return val, nil
            }

            return "", fmt.Errorf("Gauge metric '%s' not found", metric)
    }

    // SetGaugeFunc sets a gauge to a function [called at flush interval]
    func (m *CirconusMetrics) SetGaugeFunc(metric string, fn func() int64) {
            m.gfm.Lock()
            defer m.gfm.Unlock()
            m.gaugeFuncs[metric] = fn
    }

    // RemoveGaugeFunc removes a gauge function
    func (m *CirconusMetrics) RemoveGaugeFunc(metric string) {
            m.gfm.Lock()
            defer m.gfm.Unlock()
            delete(m.gaugeFuncs, metric)
    }

    // gaugeValString converts an interface value (of a supported type) to a string
    func (m *CirconusMetrics) gaugeValString(val interface{}) string {
            vs := ""
            switch v := val.(type) {
            default:
                    // ignore it, unsupported type
            case int:
                    vs = fmt.Sprintf("%d", v)
            case int8:
                    vs = fmt.Sprintf("%d", v)
            case int16:
                    vs = fmt.Sprintf("%d", v)
            case int32:
                    vs = fmt.Sprintf("%d", v)
            case int64:
                    vs = fmt.Sprintf("%d", v)
            case uint:
                    vs = fmt.Sprintf("%d", v)
            case uint8:
                    vs = fmt.Sprintf("%d", v)
            case uint16:
                    vs = fmt.Sprintf("%d", v)
            case uint32:
                    vs = fmt.Sprintf("%d", v)
            case uint64:
                    vs = fmt.Sprintf("%d", v)
            case float32:
                    vs = fmt.Sprintf("%f", v)
            case float64:
                    vs = fmt.Sprintf("%f", v)
            }
            return vs
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package circonusgometrics

    import (
            "fmt"
            "sync"

            "github.com/circonus-labs/circonusllhist"
    )

    // Histogram measures the distribution of a stream of values.
    type Histogram struct {
            name string
            hist *circonusllhist.Histogram
            rw   sync.RWMutex
    }

    // Timing adds a value to a histogram
    func (m *CirconusMetrics) Timing(metric string, val float64) {
            m.SetHistogramValue(metric, val)
    }

    // RecordValue adds a value to a histogram
    func (m *CirconusMetrics) RecordValue(metric string, val float64) {
            m.SetHistogramValue(metric, val)
    }

    // SetHistogramValue adds a value to a histogram
    func (m *CirconusMetrics) SetHistogramValue(metric string, val float64) {
            hist := m.NewHistogram(metric)

            m.hm.Lock()
            hist.rw.Lock()
            hist.hist.RecordValue(val)
            hist.rw.Unlock()
            m.hm.Unlock()
    }

    // GetHistogramTest returns the current value for a gauge. (note: it is a function specifically for "testing", disable automatic submission during testing.)
    func (m *CirconusMetrics) GetHistogramTest(metric string) ([]string, error) {
            m.hm.Lock()
            defer m.hm.Unlock()

            if hist, ok := m.histograms[metric]; ok {
                    return hist.hist.DecStrings(), nil
            }

            return []string{""}, fmt.Errorf("Histogram metric '%s' not found", metric)
    }

    // RemoveHistogram removes a histogram
    func (m *CirconusMetrics) RemoveHistogram(metric string) {
            m.hm.Lock()
            delete(m.histograms, metric)
            m.hm.Unlock()
    }

    // NewHistogram returns a histogram instance.
    func (m *CirconusMetrics) NewHistogram(metric string) *Histogram {
            m.hm.Lock()
            defer m.hm.Unlock()

            if hist, ok := m.histograms[metric]; ok {
                    return hist
            }

            hist := &Histogram{
                    name: metric,
                    hist: circonusllhist.New(),
            }

            m.histograms[metric] = hist

            return hist
    }

    // Name returns the name from a histogram instance
    func (h *Histogram) Name() string {
            return h.name
    }

    // RecordValue records the given value to a histogram instance
    func (h *Histogram) RecordValue(v float64) {
            h.rw.Lock()
            h.hist.RecordValue(v)
            h.rw.Unlock()
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package circonusgometrics

    // SetMetricTags sets the tags for the named metric and flags a check update is needed
    func (m *CirconusMetrics) SetMetricTags(name string, tags []string) bool {
            return m.check.AddMetricTags(name, tags, false)
    }

    // AddMetricTags appends tags to any existing tags for the named metric and flags a check update is needed
    func (m *CirconusMetrics) AddMetricTags(name string, tags []string) bool {
            return m.check.AddMetricTags(name, tags, true)
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package circonusgometrics

    import (
            "bytes"
            "encoding/json"
            "errors"
            "fmt"
            "io/ioutil"
            "log"
            "net"
            "net/http"
            "strconv"
            "time"

            "github.com/circonus-labs/circonus-gometrics/api"
            "github.com/hashicorp/go-retryablehttp"
    )

    func (m *CirconusMetrics) submit(output Metrics, newMetrics map[string]*api.CheckBundleMetric) {

            // if there is nowhere to send metrics to, just return.
            if !m.check.IsReady() {
                    m.Log.Printf("[WARN] check not ready, skipping metric submission")
                    return
            }

            // update check if there are any new metrics or, if metric tags have been added since last submit
            m.check.UpdateCheck(newMetrics)

            str, err := json.Marshal(output)
            if err != nil {
                    m.Log.Printf("[ERROR] marshaling output %+v", err)
                    return
            }

            numStats, err := m.trapCall(str)
            if err != nil {
                    m.Log.Printf("[ERROR] %+v\n", err)
                    return
            }

            if m.Debug {
                    m.Log.Printf("[DEBUG] %d stats sent\n", numStats)
            }
    }

    func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
            trap, err := m.check.GetSubmissionURL()
            if err != nil {
                    return 0, err
            }

            dataReader := bytes.NewReader(payload)

            req, err := retryablehttp.NewRequest("PUT", trap.URL.String(), dataReader)
            if err != nil {
                    return 0, err
            }
            req.Header.Add("Content-Type", "application/json")
            req.Header.Add("Accept", "application/json")

            // keep last HTTP error in the event of retry failure
            var lastHTTPError error
            retryPolicy := func(resp *http.Response, err error) (bool, error) {
                    if err != nil {
                            lastHTTPError = err
                            return true, err
                    }
                    // Check the response code. We retry on 500-range responses to allow
                    // the server time to recover, as 500's are typically not permanent
                    // errors and may relate to outages on the server side. This will catch
                    // invalid response codes as well, like 0 and 999.
                    if resp.StatusCode == 0 || resp.StatusCode >= 500 {
                            body, readErr := ioutil.ReadAll(resp.Body)
                            if readErr != nil {
                                    lastHTTPError = fmt.Errorf("- last HTTP error: %d %+v", resp.StatusCode, readErr)
                            } else {
                                    lastHTTPError = fmt.Errorf("- last HTTP error: %d %s", resp.StatusCode, string(body))
                            }
                            return true, nil
                    }
                    return false, nil
            }

            client := retryablehttp.NewClient()
            if trap.URL.Scheme == "https" {
                    client.HTTPClient.Transport = &http.Transport{
                            Proxy: http.ProxyFromEnvironment,
                            Dial: (&net.Dialer{
                                    Timeout:   30 * time.Second,
                                    KeepAlive: 30 * time.Second,
                            }).Dial,
                            TLSHandshakeTimeout: 10 * time.Second,
                            TLSClientConfig:     trap.TLS,
                            DisableKeepAlives:   true,
                            MaxIdleConnsPerHost: -1,
                            DisableCompression:  true,
                    }
            } else {
                    client.HTTPClient.Transport = &http.Transport{
                            Proxy: http.ProxyFromEnvironment,
                            Dial: (&net.Dialer{
                                    Timeout:   30 * time.Second,
                                    KeepAlive: 30 * time.Second,
                            }).Dial,
                            TLSHandshakeTimeout: 10 * time.Second,
                            DisableKeepAlives:   true,
                            MaxIdleConnsPerHost: -1,
                            DisableCompression:  true,
                    }
            }
            client.RetryWaitMin = 1 * time.Second
            client.RetryWaitMax = 5 * time.Second
            client.RetryMax = 3
            // retryablehttp only groks log or no log
            // but, outputs everything as [DEBUG] messages
            if m.Debug {
                    client.Logger = m.Log
            } else {
                    client.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
            }
            client.CheckRetry = retryPolicy

            attempts := -1
            client.RequestLogHook = func(logger *log.Logger, req *http.Request, retryNumber int) {
                    attempts = retryNumber
            }

            resp, err := client.Do(req)
            if err != nil {
                    if lastHTTPError != nil {
                            return 0, fmt.Errorf("[ERROR] submitting: %+v %+v", err, lastHTTPError)
                    }
                    if attempts == client.RetryMax {
                            m.check.RefreshTrap()
                    }
                    return 0, err
            }

            defer resp.Body.Close()

            // no content - expected result from circonus-agent when metrics accepted
            if resp.StatusCode == http.StatusNoContent {
                    return -1, nil
            }

            body, err := ioutil.ReadAll(resp.Body)
            if err != nil {
                    m.Log.Printf("[ERROR] reading body, proceeding. %s\n", err)
            }

            var response map[string]interface{}
            if err := json.Unmarshal(body, &response); err != nil {
                    m.Log.Printf("[ERROR] parsing body, proceeding. %v (%s)\n", err, body)
            }

            if resp.StatusCode != http.StatusOK {
                    return 0, errors.New("[ERROR] bad response code: " + strconv.Itoa(resp.StatusCode))
            }
            switch v := response["stats"].(type) {
            case float64:
                    return int(v), nil
            case int:
                    return v, nil
            default:
            }
            return 0, errors.New("[ERROR] bad response type")
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package circonusgometrics

    // A Text metric is an arbitrary string
    //

    // SetText sets a text metric
    func (m *CirconusMetrics) SetText(metric string, val string) {
            m.SetTextValue(metric, val)
    }

    // SetTextValue sets a text metric
    func (m *CirconusMetrics) SetTextValue(metric string, val string) {
            m.tm.Lock()
            defer m.tm.Unlock()
            m.text[metric] = val
    }

    // RemoveText removes a text metric
    func (m *CirconusMetrics) RemoveText(metric string) {
            m.tm.Lock()
            defer m.tm.Unlock()
            delete(m.text, metric)
    }

    // SetTextFunc sets a text metric to a function [called at flush interval]
    func (m *CirconusMetrics) SetTextFunc(metric string, fn func() string) {
            m.tfm.Lock()
            defer m.tfm.Unlock()
            m.textFuncs[metric] = fn
    }

    // RemoveTextFunc a text metric function
    func (m *CirconusMetrics) RemoveTextFunc(metric string) {
            m.tfm.Lock()
            defer m.tfm.Unlock()
            delete(m.textFuncs, metric)
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package circonusgometrics

    import (
            "net/http"
            "time"
    )

    // TrackHTTPLatency wraps Handler functions registered with an http.ServerMux tracking latencies.
    // Metrics are of the for go`HTTP`<method>`<name>`latency and are tracked in a histogram in units
    // of seconds (as a float64) providing nanosecond ganularity.
    func (m *CirconusMetrics) TrackHTTPLatency(name string, handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
            return func(rw http.ResponseWriter, req *http.Request) {
                    start := time.Now().UnixNano()
                    handler(rw, req)
                    elapsed := time.Now().UnixNano() - start
                    //hist := m.NewHistogram("go`HTTP`" + req.Method + "`" + name + "`latency")
                    m.RecordValue("go`HTTP`"+req.Method+"`"+name+"`latency", float64(elapsed)/float64(time.Second))
            }
    }

    // Copyright 2016 Circonus, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.

    package circonusgometrics

    import (
            "github.com/circonus-labs/circonusllhist"
    )

    // Reset removes all existing counters and gauges.
    func (m *CirconusMetrics) Reset() {
            m.cm.Lock()
            defer m.cm.Unlock()

            m.cfm.Lock()
            defer m.cfm.Unlock()

            m.gm.Lock()
            defer m.gm.Unlock()

            m.gfm.Lock()
            defer m.gfm.Unlock()

            m.hm.Lock()
            defer m.hm.Unlock()

            m.tm.Lock()
            defer m.tm.Unlock()

            m.tfm.Lock()
            defer m.tfm.Unlock()

            m.counters = make(map[string]uint64)
            m.counterFuncs = make(map[string]func() uint64)
            m.gauges = make(map[string]string)
            m.gaugeFuncs = make(map[string]func() int64)
            m.histograms = make(map[string]*Histogram)
            m.text = make(map[string]string)
            m.textFuncs = make(map[string]func() string)
    }

    // snapshot returns a copy of the values of all registered counters and gauges.
    func (m *CirconusMetrics) snapshot() (c map[string]uint64, g map[string]string, h map[string]*circonusllhist.Histogram, t map[string]string) {
            c = m.snapCounters()
            g = m.snapGauges()
            h = m.snapHistograms()
            t = m.snapText()

            return
    }

    func (m *CirconusMetrics) snapCounters() map[string]uint64 {
            m.cm.Lock()
            defer m.cm.Unlock()
            m.cfm.Lock()
            defer m.cfm.Unlock()

            c := make(map[string]uint64, len(m.counters)+len(m.counterFuncs))

            for n, v := range m.counters {
                    c[n] = v
            }
            if m.resetCounters && len(c) > 0 {
                    m.counters = make(map[string]uint64)
            }

            for n, f := range m.counterFuncs {
                    c[n] = f()
            }
            if m.resetCounters && len(c) > 0 {
                    m.counterFuncs = make(map[string]func() uint64)
            }

            return c
    }

    func (m *CirconusMetrics) snapGauges() map[string]string {
            m.gm.Lock()
            defer m.gm.Unlock()
            m.gfm.Lock()
            defer m.gfm.Unlock()

            g := make(map[string]string, len(m.gauges)+len(m.gaugeFuncs))

            for n, v := range m.gauges {
                    g[n] = v
            }
            if m.resetGauges && len(g) > 0 {
                    m.gauges = make(map[string]string)
            }

            for n, f := range m.gaugeFuncs {
                    g[n] = m.gaugeValString(f())
            }
            if m.resetGauges && len(g) > 0 {
                    m.gaugeFuncs = make(map[string]func() int64)
            }

            return g
    }

    func (m *CirconusMetrics) snapHistograms() map[string]*circonusllhist.Histogram {
            m.hm.Lock()
            defer m.hm.Unlock()

            h := make(map[string]*circonusllhist.Histogram, len(m.histograms))

            for n, hist := range m.histograms {
                    hist.rw.Lock()
                    h[n] = hist.hist.CopyAndReset()
                    hist.rw.Unlock()
            }
            if m.resetHistograms && len(h) > 0 {
                    m.histograms = make(map[string]*Histogram)
            }

            return h
    }

    func (m *CirconusMetrics) snapText() map[string]string {
            m.tm.Lock()
            defer m.tm.Unlock()
            m.tfm.Lock()
            defer m.tfm.Unlock()

            t := make(map[string]string, len(m.text)+len(m.textFuncs))

            for n, v := range m.text {
                    t[n] = v
            }
            if m.resetText && len(t) > 0 {
                    m.text = make(map[string]string)
            }

            for n, f := range m.textFuncs {
                    t[n] = f()
            }
            if m.resetText && len(t) > 0 {
                    m.textFuncs = make(map[string]func() string)
            }

            return t
    }