// 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 ¬es } // 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 }