diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/OPTIONS.md b/vendor/github.com/circonus-labs/circonus-gometrics/OPTIONS.md new file mode 100644 index 000000000..3926c3e63 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/OPTIONS.md @@ -0,0 +1,107 @@ +## Circonus gometrics options + +### Example defaults +```go +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path" + + cgm "github.com/circonus-labs/circonus-gometrics" +) + +func main() { + cfg := &cgm.Config{} + + // Defaults + + // General + cfg.Debug = false + cfg.Log = log.New(ioutil.Discard, "", log.LstdFlags) + cfg.Interval = "10s" + cfg.ResetCounters = "true" + cfg.ResetGauges = "true" + cfg.ResetHistograms = "true" + cfg.ResetText = "true" + + // API + cfg.CheckManager.API.TokenKey = "" + cfg.CheckManager.API.TokenApp = "circonus-gometrics" + cfg.CheckManager.API.TokenURL = "https://api.circonus.com/v2" + + // Check + _, an := path.Split(os.Args[0]) + hn, _ := os.Hostname() + cfg.CheckManager.Check.ID = "" + cfg.CheckManager.Check.SubmissionURL = "" + cfg.CheckManager.Check.InstanceID = fmt.Sprintf("%s:%s", hn, an) + cfg.CheckManager.Check.TargetHost = cfg.CheckManager.Check.InstanceID + cfg.CheckManager.Check.DisplayName = cfg.CheckManager.Check.InstanceID + cfg.CheckManager.Check.SearchTag = fmt.Sprintf("service:%s", an) + cfg.CheckManager.Check.Tags = "" + cfg.CheckManager.Check.Secret = "" // randomly generated sha256 hash + cfg.CheckManager.Check.MaxURLAge = "5m" + cfg.CheckManager.Check.ForceMetricActivation = "false" + + // Broker + cfg.CheckManager.Broker.ID = "" + cfg.CheckManager.Broker.SelectTag = "" + cfg.CheckManager.Broker.MaxResponseTime = "500ms" + + // create a new cgm instance and start sending metrics... + // see the complete example in the main README. +} +``` + +## Options +| Option | Default | Description | +| ------ | ------- | ----------- | +| General || +| `cfg.Log` | none | log.Logger instance to send logging messages. Default is to discard messages. If Debug is turned on and no instance is specified, messages will go to stderr. | +| `cfg.Debug` | false | Turn on debugging messages. | +| `cfg.Interval` | "10s" | Interval at which metrics are flushed and sent to Circonus. Set to "0s" to disable automatic flush (note, if disabled, `cgm.Flush()` must be called manually to send metrics to Circonus).| +| `cfg.ResetCounters` | "true" | Reset counter metrics after each submission. Change to "false" to retain (and continue submitting) the last value.| +| `cfg.ResetGauges` | "true" | Reset gauge metrics after each submission. Change to "false" to retain (and continue submitting) the last value.| +| `cfg.ResetHistograms` | "true" | Reset histogram metrics after each submission. Change to "false" to retain (and continue submitting) the last value.| +| `cfg.ResetText` | "true" | Reset text metrics after each submission. Change to "false" to retain (and continue submitting) the last value.| +|API|| +| `cfg.CheckManager.API.TokenKey` | "" | [Circonus API Token key](https://login.circonus.com/user/tokens) | +| `cfg.CheckManager.API.TokenApp` | "circonus-gometrics" | App associated with API token | +| `cfg.CheckManager.API.URL` | "https://api.circonus.com/v2" | Circonus API URL | +|Check|| +| `cfg.CheckManager.Check.ID` | "" | Check ID of previously created check. (*Note: **check id** not **check bundle id**.*) | +| `cfg.CheckManager.Check.SubmissionURL` | "" | Submission URL of previously created check. | +| `cfg.CheckManager.Check.InstanceID` | hostname:program name | An identifier for the 'group of metrics emitted by this process or service'. | +| `cfg.CheckManager.Check.TargetHost` | InstanceID | Explicit setting of `check.target`. | +| `cfg.CheckManager.Check.DisplayName` | InstanceID | Custom `check.display_name`. Shows in UI check list. | +| `cfg.CheckManager.Check.SearchTag` | service:program name | Specific tag used to search for an existing check when neither SubmissionURL nor ID are provided. | +| `cfg.CheckManager.Check.Tags` | "" | List (comma separated) of tags to add to check when it is being created. The SearchTag will be added to the list. | +| `cfg.CheckManager.Check.Secret` | random generated | A secret to use for when creating an httptrap check. | +| `cfg.CheckManager.Check.MaxURLAge` | "5m" | Maximum amount of time to retry a [failing] submission URL before refreshing it. | +| `cfg.CheckManager.Check.ForceMetricActivation` | "false" | 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. | +|Broker|| +| `cfg.CheckManager.Broker.ID` | "" | ID of a specific broker to use when creating a check. Default is to use a random enterprise broker or the public Circonus default broker. | +| `cfg.CheckManager.Broker.SelectTag` | "" | Used to select a broker with the same tag(s). If more than one broker has the tag(s), one will be selected randomly from the resulting list. (e.g. could be used to select one from a list of brokers serving a specific colo/region. "dc:sfo", "loc:nyc,dc:nyc01", "zone:us-west") | +| `cfg.CheckManager.Broker.MaxResponseTime` | "500ms" | Maximum amount time to wait for a broker connection test to be considered valid. (if latency is > the broker will be considered invalid and not available for selection.) | + +## Notes: + +* All options are *strings* with the following exceptions: + * `cfg.Log` - an instance of [`log.Logger`](https://golang.org/pkg/log/#Logger) or something else (e.g. [logrus](https://github.com/Sirupsen/logrus)) which can be used to satisfy the interface requirements. + * `cfg.Debug` - a boolean true|false. +* At a minimum, one of either `API.TokenKey` or `Check.SubmissionURL` is **required** for cgm to function. +* Check management can be disabled by providing a `Check.SubmissionURL` without an `API.TokenKey`. Note: the supplied URL needs to be http or the broker needs to be running with a cert which can be verified. Otherwise, the `API.TokenKey` will be required to retrieve the correct CA certificate to validate the broker's cert for the SSL connection. +* A note on `Check.InstanceID`, the instance id is used to consistently identify a check. The display name can be changed in the UI. The hostname may be ephemeral. For metric continuity, the instance id is used to locate existing checks. Since the check.target is never actually used by an httptrap check it is more decorative than functional, a valid FQDN is not required for an httptrap check.target. But, using instance id as the target can pollute the Host list in the UI with host:application specific entries. +* Check identification precedence + 1. Check SubmissionURL + 2. Check ID + 3. Search + 1. Search for an active httptrap check for TargetHost which has the SearchTag + 2. Search for an active httptrap check which has the SearchTag and the InstanceID in the notes field + 3. Create a new check +* Broker selection + 1. If Broker.ID or Broker.SelectTag are not specified, a broker will be selected randomly from the list of brokers available to the API token. Enterprise brokers take precedence. A viable broker is "active", has the "httptrap" module enabled, and responds within Broker.MaxResponseTime. diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/README.md b/vendor/github.com/circonus-labs/circonus-gometrics/README.md index 77daae05b..a6291ef1e 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/README.md +++ b/vendor/github.com/circonus-labs/circonus-gometrics/README.md @@ -1,189 +1,184 @@ # Circonus metrics tracking for Go applications -This library supports named counters, gauges and histograms. -It also provides convenience wrappers for registering latency -instrumented functions with Go's builtin http server. +This library supports named counters, gauges and histograms. It also provides convenience wrappers for registering latency instrumented functions with Go's builtin http server. -Initializing only requires setting an ApiToken. +Initializing only requires setting an [API Token](https://login.circonus.com/user/tokens) at a minimum. + +## Options + +See [OPTIONS.md](OPTIONS.md) for information on all of the available cgm options. ## Example -**rough and simple** +### Bare bones minimum + +A working cut-n-past example. Simply set the required environment variable `CIRCONUS_API_TOKEN` and run. ```go package main import ( - "log" - "math/rand" - "os" - "time" + "log" + "math/rand" + "os" + "os/signal" + "syscall" + "time" - cgm "github.com/circonus-labs/circonus-gometrics" + cgm "github.com/circonus-labs/circonus-gometrics" ) func main() { logger := log.New(os.Stdout, "", log.LstdFlags) - logger.Println("Configuring cgm") + logger.Println("Configuring cgm") - cmc := &cgm.Config{} + cmc := &cgm.Config{} + cmc.Debug := false // set to true for debug messages + cmc.Log = logger - // Interval at which metrics are submitted to Circonus, default: 10 seconds - // cmc.Interval = "10s" // 10 seconds + // Circonus API Token key (https://login.circonus.com/user/tokens) + cmc.CheckManager.API.TokenKey = os.Getenv("CIRCONUS_API_TOKEN") - // Enable debug messages, default: false - cmc.Debug = true + logger.Println("Creating new cgm instance") - // Send debug messages to specific log.Logger instance - // default: if debug stderr, else, discard - cmc.Log = logger + metrics, err := cgm.NewCirconusMetrics(cmc) + if err != nil { + logger.Println(err) + os.Exit(1) + } - // Reset counter metrics after each submission, default: "true" - // Change to "false" to retain (and continue submitting) the last value. - // cmc.ResetCounters = "true" - - // Reset gauge metrics after each submission, default: "true" - // Change to "false" to retain (and continue submitting) the last value. - // cmc.ResetGauges = "true" - - // Reset histogram metrics after each submission, default: "true" - // Change to "false" to retain (and continue submitting) the last value. - // cmc.ResetHistograms = "true" - - // Reset text metrics after each submission, default: "true" - // Change to "false" to retain (and continue submitting) the last value. - // cmc.ResetText = "true" - - // Circonus API configuration options - // - // Token, no default (blank disables check manager) - cmc.CheckManager.API.TokenKey = os.Getenv("CIRCONUS_API_TOKEN") - // App name, default: circonus-gometrics - cmc.CheckManager.API.TokenApp = os.Getenv("CIRCONUS_API_APP") - // URL, default: https://api.circonus.com/v2 - cmc.CheckManager.API.URL = os.Getenv("CIRCONUS_API_URL") - - // Check configuration options - // - // precedence 1 - explicit submission_url - // precedence 2 - specific check id (note: not a check bundle id) - // precedence 3 - search using instanceId and searchTag - // otherwise: if an applicable check is NOT specified or found, an - // attempt will be made to automatically create one - // - // Submission URL for an existing [httptrap] check - cmc.CheckManager.Check.SubmissionURL = os.Getenv("CIRCONUS_SUBMISION_URL") - - // ID of an existing [httptrap] check (note: check id not check bundle id) - cmc.CheckManager.Check.ID = os.Getenv("CIRCONUS_CHECK_ID") - - // if neither a submission url nor check id are provided, an attempt will be made to find an existing - // httptrap check by using the circonus api to search for a check matching the following criteria: - // an active check, - // of type httptrap, - // where the target/host is equal to InstanceId - see below - // and the check has a tag equal to SearchTag - see below - // Instance ID - an identifier for the 'group of metrics emitted by this process or service' - // this is used as the value for check.target (aka host) - // default: 'hostname':'program name' - // note: for a persistent instance that is ephemeral or transient where metric continuity is - // desired set this explicitly so that the current hostname will not be used. - // cmc.CheckManager.Check.InstanceID = "" - - // Search tag - specific tag(s) used in conjunction with isntanceId to search for an - // existing check. comma separated string of tags (spaces will be removed, no commas - // in tag elements). - // default: service:application name (e.g. service:consul service:nomad etc.) - // cmc.CheckManager.Check.SearchTag = "" - - // Check secret, default: generated when a check needs to be created - // cmc.CheckManager.Check.Secret = "" - - // Additional tag(s) to add when *creating* a check. comma separated string - // of tags (spaces will be removed, no commas in tag elements). - // (e.g. group:abc or service_role:agent,group:xyz). - // default: none - // cmc.CheckManager.Check.Tags = "" - - // 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) default 5 minutes - // cmc.CheckManager.Check.MaxURLAge = "5m" - - // custom display name for check, default: "InstanceId /cgm" - // cmc.CheckManager.Check.DisplayName = "" - - // 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" - // cmc.CheckManager.Check.ForceMetricActivation = "false" - - // Broker configuration options - // - // Broker ID of specific broker to use, default: random enterprise broker or - // Circonus default if no enterprise brokers are available. - // default: only used if set - // cmc.CheckManager.Broker.ID = "" - - // used to select a broker with the same tag(s) (e.g. can be used to dictate that a broker - // serving a specific location should be used. "dc:sfo", "loc:nyc,dc:nyc01", "zone:us-west") - // if more than one broker has the tag(s), one will be selected randomly from the resulting - // list. comma separated string of tags (spaces will be removed, no commas in tag elements). - // default: none - // cmc.CheckManager.Broker.SelectTag = "" - - // longest time to wait for a broker connection (if latency is > the broker will - // be considered invalid and not available for selection.), default: 500 milliseconds - // cmc.CheckManager.Broker.MaxResponseTime = "500ms" - - // note: if broker Id or SelectTag are not specified, a broker will be selected randomly - // from the list of brokers available to the api token. enterprise brokers take precedence - // viable brokers are "active", have the "httptrap" module enabled, are reachable and respond - // within MaxResponseTime. - - logger.Println("Creating new cgm instance") - - metrics, err := cgm.NewCirconusMetrics(cmc) - if err != nil { - panic(err) - } - - src := rand.NewSource(time.Now().UnixNano()) - rnd := rand.New(src) - - logger.Println("Starting cgm internal auto-flush timer") - metrics.Start() + src := rand.NewSource(time.Now().UnixNano()) + rnd := rand.New(src) logger.Println("Adding ctrl-c trap") - c := make(chan os.Signal, 2) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - logger.Println("Received CTRL-C, flushing outstanding metrics before exit") - metrics.Flush() - os.Exit(0) - }() + c := make(chan os.Signal, 2) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + logger.Println("Received CTRL-C, flushing outstanding metrics before exit") + metrics.Flush() + os.Exit(0) + }() + + logger.Println("Starting to send metrics") + + // number of "sets" of metrics to send + max := 60 + + for i := 1; i < max; i++ { + logger.Printf("\tmetric set %d of %d", i, 60) + metrics.Timing("foo", rnd.Float64()*10) + metrics.Increment("bar") + metrics.Gauge("baz", 10) + time.Sleep(time.Second) + } + + metrics.SetText("fini", "complete") + + logger.Println("Flushing any outstanding metrics manually") + metrics.Flush() +} +``` + +### A more complete example + +A working, cut-n-paste example with all options available for modification. Also, demonstrates metric tagging. + +```go +package main + +import ( + "log" + "math/rand" + "os" + "os/signal" + "syscall" + "time" + + cgm "github.com/circonus-labs/circonus-gometrics" +) + +func main() { + + logger := log.New(os.Stdout, "", log.LstdFlags) + + logger.Println("Configuring cgm") + + cmc := &cgm.Config{} + + // General + + cmc.Interval = "10s" + cmc.Log = logger + cmc.Debug = false + cmc.ResetCounters = "true" + cmc.ResetGauges = "true" + cmc.ResetHistograms = "true" + cmc.ResetText = "true" + + // Circonus API configuration options + cmc.CheckManager.API.TokenKey = os.Getenv("CIRCONUS_API_TOKEN") + cmc.CheckManager.API.TokenApp = os.Getenv("CIRCONUS_API_APP") + cmc.CheckManager.API.URL = os.Getenv("CIRCONUS_API_URL") + + // Check configuration options + cmc.CheckManager.Check.SubmissionURL = os.Getenv("CIRCONUS_SUBMISION_URL") + cmc.CheckManager.Check.ID = os.Getenv("CIRCONUS_CHECK_ID") + cmc.CheckManager.Check.InstanceID = "" + cmc.CheckManager.Check.DisplayName = "" + cmc.CheckManager.Check.TargetHost = "" + // if hn, err := os.Hostname(); err == nil { + // cmc.CheckManager.Check.TargetHost = hn + // } + cmc.CheckManager.Check.SearchTag = "" + cmc.CheckManager.Check.Secret = "" + cmc.CheckManager.Check.Tags = "" + cmc.CheckManager.Check.MaxURLAge = "5m" + cmc.CheckManager.Check.ForceMetricActivation = "false" + + // Broker configuration options + cmc.CheckManager.Broker.ID = "" + cmc.CheckManager.Broker.SelectTag = "" + cmc.CheckManager.Broker.MaxResponseTime = "500ms" + + logger.Println("Creating new cgm instance") + + metrics, err := cgm.NewCirconusMetrics(cmc) + if err != nil { + logger.Println(err) + os.Exit(1) + } + + src := rand.NewSource(time.Now().UnixNano()) + rnd := rand.New(src) + + logger.Println("Adding ctrl-c trap") + c := make(chan os.Signal, 2) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + logger.Println("Received CTRL-C, flushing outstanding metrics before exit") + metrics.Flush() + os.Exit(0) + }() // Add metric tags (append to any existing tags on specified metric) metrics.AddMetricTags("foo", []string{"cgm:test"}) metrics.AddMetricTags("baz", []string{"cgm:test"}) - logger.Println("Starting to send metrics") + logger.Println("Starting to send metrics") - // number of "sets" of metrics to send - max := 60 + // number of "sets" of metrics to send + max := 60 - for i := 1; i < max; i++ { - logger.Printf("\tmetric set %d of %d", i, 60) + for i := 1; i < max; i++ { + logger.Printf("\tmetric set %d of %d", i, 60) - metrics.Timing("foo", rnd.Float64()*10) - metrics.Increment("bar") - metrics.Gauge("baz", 10) + metrics.Timing("foo", rnd.Float64()*10) + metrics.Increment("bar") + metrics.Gauge("baz", 10) if i == 35 { // Set metric tags (overwrite current tags on specified metric) @@ -191,23 +186,23 @@ func main() { } time.Sleep(time.Second) - } + } - logger.Println("Flushing any outstanding metrics manually") - metrics.Flush() + logger.Println("Flushing any outstanding metrics manually") + metrics.Flush() } ``` ### HTTP Handler wrapping -``` +```go http.HandleFunc("/", metrics.TrackHTTPLatency("/", handler_func)) ``` ### HTTP latency example -``` +```go package main import ( @@ -225,7 +220,6 @@ func main() { if err != nil { panic(err) } - metrics.Start() http.HandleFunc("/", metrics.TrackHTTPLatency("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:]) diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/README.md b/vendor/github.com/circonus-labs/circonus-gometrics/api/README.md new file mode 100644 index 000000000..8f286b79f --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/README.md @@ -0,0 +1,163 @@ +## Circonus API package + +Full api documentation (for using *this* package) is available at [godoc.org](https://godoc.org/github.com/circonus-labs/circonus-gometrics/api). Links in the lists below go directly to the generic Circonus API documentation for the endpoint. + +### Straight [raw] API access + +* Get +* Post (for creates) +* Put (for updates) +* Delete + +### Helpers for currently supported API endpoints + +> Note, these interfaces are still being actively developed. For example, many of the `New*` methods only return an empty struct; sensible defaults will be added going forward. Other, common helper methods for the various endpoints may be added as use cases emerge. The organization +of the API may change if common use contexts would benefit significantly. + +* [Account](https://login.circonus.com/resources/api/calls/account) + * FetchAccount + * FetchAccounts + * UpdateAccount + * SearchAccounts +* [Acknowledgement](https://login.circonus.com/resources/api/calls/acknowledgement) + * NewAcknowledgement + * FetchAcknowledgement + * FetchAcknowledgements + * UpdateAcknowledgement + * CreateAcknowledgement + * DeleteAcknowledgement + * DeleteAcknowledgementByCID + * SearchAcknowledgements +* [Alert](https://login.circonus.com/resources/api/calls/alert) + * FetchAlert + * FetchAlerts + * SearchAlerts +* [Annotation](https://login.circonus.com/resources/api/calls/annotation) + * NewAnnotation + * FetchAnnotation + * FetchAnnotations + * UpdateAnnotation + * CreateAnnotation + * DeleteAnnotation + * DeleteAnnotationByCID + * SearchAnnotations +* [Broker](https://login.circonus.com/resources/api/calls/broker) + * FetchBroker + * FetchBrokers + * SearchBrokers +* [Check Bundle](https://login.circonus.com/resources/api/calls/check_bundle) + * NewCheckBundle + * FetchCheckBundle + * FetchCheckBundles + * UpdateCheckBundle + * CreateCheckBundle + * DeleteCheckBundle + * DeleteCheckBundleByCID + * SearchCheckBundles +* [Check Bundle Metrics](https://login.circonus.com/resources/api/calls/check_bundle_metrics) + * FetchCheckBundleMetrics + * UpdateCheckBundleMetrics +* [Check](https://login.circonus.com/resources/api/calls/check) + * FetchCheck + * FetchChecks + * SearchChecks +* [Contact Group](https://login.circonus.com/resources/api/calls/contact_group) + * NewContactGroup + * FetchContactGroup + * FetchContactGroups + * UpdateContactGroup + * CreateContactGroup + * DeleteContactGroup + * DeleteContactGroupByCID + * SearchContactGroups +* [Dashboard](https://login.circonus.com/resources/api/calls/dashboard) -- note, this is a work in progress, the methods/types may still change + * NewDashboard + * FetchDashboard + * FetchDashboards + * UpdateDashboard + * CreateDashboard + * DeleteDashboard + * DeleteDashboardByCID + * SearchDashboards +* [Graph](https://login.circonus.com/resources/api/calls/graph) + * NewGraph + * FetchGraph + * FetchGraphs + * UpdateGraph + * CreateGraph + * DeleteGraph + * DeleteGraphByCID + * SearchGraphs +* [Metric Cluster](https://login.circonus.com/resources/api/calls/metric_cluster) + * NewMetricCluster + * FetchMetricCluster + * FetchMetricClusters + * UpdateMetricCluster + * CreateMetricCluster + * DeleteMetricCluster + * DeleteMetricClusterByCID + * SearchMetricClusters +* [Metric](https://login.circonus.com/resources/api/calls/metric) + * FetchMetric + * FetchMetrics + * UpdateMetric + * SearchMetrics +* [Maintenance window](https://login.circonus.com/resources/api/calls/maintenance) + * NewMaintenanceWindow + * FetchMaintenanceWindow + * FetchMaintenanceWindows + * UpdateMaintenanceWindow + * CreateMaintenanceWindow + * DeleteMaintenanceWindow + * DeleteMaintenanceWindowByCID + * SearchMaintenanceWindows +* [Outlier Report](https://login.circonus.com/resources/api/calls/outlier_report) + * NewOutlierReport + * FetchOutlierReport + * FetchOutlierReports + * UpdateOutlierReport + * CreateOutlierReport + * DeleteOutlierReport + * DeleteOutlierReportByCID + * SearchOutlierReports +* [Provision Broker](https://login.circonus.com/resources/api/calls/provision_broker) + * NewProvisionBroker + * FetchProvisionBroker + * UpdateProvisionBroker + * CreateProvisionBroker +* [Rule Set](https://login.circonus.com/resources/api/calls/rule_set) + * NewRuleset + * FetchRuleset + * FetchRulesets + * UpdateRuleset + * CreateRuleset + * DeleteRuleset + * DeleteRulesetByCID + * SearchRulesets +* [Rule Set Group](https://login.circonus.com/resources/api/calls/rule_set_group) + * NewRulesetGroup + * FetchRulesetGroup + * FetchRulesetGroups + * UpdateRulesetGroup + * CreateRulesetGroup + * DeleteRulesetGroup + * DeleteRulesetGroupByCID + * SearchRulesetGroups +* [User](https://login.circonus.com/resources/api/calls/user) + * FetchUser + * FetchUsers + * UpdateUser + * SearchUsers +* [Worksheet](https://login.circonus.com/resources/api/calls/worksheet) + * NewWorksheet + * FetchWorksheet + * FetchWorksheets + * UpdateWorksheet + * CreateWorksheet + * DeleteWorksheet + * DeleteWorksheetByCID + * SearchWorksheets + +--- + +Unless otherwise noted, the source files are distributed under the BSD-style license found in the LICENSE file. diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/account.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/account.go new file mode 100644 index 000000000..dd8ff577d --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/account.go @@ -0,0 +1,181 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/acknowledgement.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/acknowledgement.go new file mode 100644 index 000000000..f6da51d4d --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/acknowledgement.go @@ -0,0 +1,190 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/alert.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/alert.go new file mode 100644 index 000000000..a242d3d85 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/alert.go @@ -0,0 +1,131 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/annotation.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/annotation.go new file mode 100644 index 000000000..589ec6da9 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/annotation.go @@ -0,0 +1,223 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/api.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/api.go index f640c54d0..73480e4c4 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/api/api.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/api.go @@ -2,24 +2,38 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package api provides methods for interacting with the Circonus API package api import ( "bytes" + crand "crypto/rand" "errors" "fmt" "io/ioutil" "log" + "math" + "math/big" + "math/rand" "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" @@ -35,20 +49,20 @@ type TokenKeyType string // TokenAppType - Circonus API Token app name type TokenAppType string -// IDType Circonus object id (numeric portion of cid) -type IDType int - // CIDType Circonus object cid -type CIDType string +type CIDType *string + +// IDType Circonus object id +type IDType int // URLType submission url type type URLType string -// SearchQueryType search query +// SearchQueryType search query (see: https://login.circonus.com/resources/api#searching) type SearchQueryType string -// SearchFilterType search filter -type SearchFilterType 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 @@ -64,15 +78,27 @@ type Config struct { // API Circonus API type API struct { - apiURL *url.URL - key TokenKeyType - app TokenAppType - Debug bool - Log *log.Logger + apiURL *url.URL + key TokenKeyType + app TokenAppType + Debug bool + Log *log.Logger + useExponentialBackoff bool + useExponentialBackoffmu sync.Mutex } -// NewAPI returns a new Circonus API +// 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)") @@ -97,6 +123,7 @@ func NewAPI(ac *Config) (*API, error) { 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) @@ -104,7 +131,14 @@ func NewAPI(ac *Config) (*API, error) { return nil, err } - a := &API{apiURL, key, app, ac.Debug, ac.Log} + a := &API{ + apiURL: apiURL, + key: key, + app: app, + Debug: ac.Debug, + Log: ac.Log, + useExponentialBackoff: false, + } a.Debug = ac.Debug a.Log = ac.Log @@ -118,48 +152,104 @@ func NewAPI(ac *Config) (*API, error) { 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.apiCall("GET", reqPath, nil) + return a.apiRequest("GET", reqPath, nil) } // Delete API request func (a *API) Delete(reqPath string) ([]byte, error) { - return a.apiCall("DELETE", reqPath, nil) + return a.apiRequest("DELETE", reqPath, nil) } // Post API request func (a *API) Post(reqPath string, data []byte) ([]byte, error) { - return a.apiCall("POST", reqPath, data) + return a.apiRequest("POST", reqPath, data) } // Put API request func (a *API) Put(reqPath string, data []byte) ([]byte, error) { - return a.apiCall("PUT", reqPath, data) + 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) { - dataReader := bytes.NewReader(data) reqURL := a.apiURL.String() + if reqPath == "" { + return nil, errors.New("Invalid URL path") + } if reqPath[:1] != "/" { reqURL += "/" } - if reqPath[:3] == "/v2" { + if len(reqPath) >= 3 && reqPath[:3] == "/v2" { reqURL += reqPath[3:len(reqPath)] } else { reqURL += reqPath } - 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)) - // keep last HTTP error in the event of retry failure var lastHTTPError error retryPolicy := func(resp *http.Response, err error) (bool, error) { @@ -172,24 +262,47 @@ func (a *API) apiCall(reqMethod string, reqPath string, data []byte) ([]byte, er // 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 || resp.StatusCode >= 500 || resp.StatusCode == 429 { + 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("- last HTTP error: %d %+v", resp.StatusCode, readErr) + lastHTTPError = fmt.Errorf("- response: %d %s", resp.StatusCode, readErr.Error()) } else { - lastHTTPError = fmt.Errorf("- last HTTP error: %d %s", resp.StatusCode, string(body)) + 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)) + client := retryablehttp.NewClient() - client.RetryWaitMin = minRetryWait - client.RetryWaitMax = maxRetryWait - client.RetryMax = maxRetries + 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 - // but, outputs everything as [DEBUG] messages if a.Debug { client.Logger = a.Log } else { diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/broker.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/broker.go index 76dfebeca..459fda6df 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/api/broker.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/broker.go @@ -2,51 +2,69 @@ // 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" - "strings" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" ) -// BrokerDetail instance attributes +// BrokerDetail defines instance attributes type BrokerDetail struct { - CN string `json:"cn"` - ExternalHost string `json:"external_host"` - ExternalPort int `json:"external_port"` - IP string `json:"ipaddress"` - MinVer int `json:"minimum_version_required"` - Modules []string `json:"modules"` - Port int `json:"port"` - Skew string `json:"skew"` - Status string `json:"status"` - Version int `json:"version"` + 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 definition +// Broker defines a broker. See https://login.circonus.com/resources/api/calls/broker for more information. type Broker struct { - Cid string `json:"_cid"` - Details []BrokerDetail `json:"_details"` - Latitude string `json:"_latitude"` - Longitude string `json:"_longitude"` - Name string `json:"_name"` - Tags []string `json:"_tags"` - Type string `json:"_type"` + 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 } -// FetchBrokerByID fetch a broker configuration by [group]id -func (a *API) FetchBrokerByID(id IDType) (*Broker, error) { - cid := CIDType(fmt.Sprintf("/broker/%d", id)) - return a.FetchBrokerByCID(cid) -} +// 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]") + } -// FetchBrokerByCID fetch a broker configuration by cid -func (a *API) FetchBrokerByCID(cid CIDType) (*Broker, error) { - result, err := a.Get(string(cid)) + 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 { @@ -57,32 +75,9 @@ func (a *API) FetchBrokerByCID(cid CIDType) (*Broker, error) { } -// FetchBrokerListByTag return list of brokers with a specific tag -func (a *API) FetchBrokerListByTag(searchTag TagType) ([]Broker, error) { - query := SearchQueryType(fmt.Sprintf("f__tags_has=%s", strings.Replace(strings.Join(searchTag, ","), ",", "&f__tags_has=", -1))) - return a.BrokerSearch(query) -} - -// BrokerSearch return a list of brokers matching a query/filter -func (a *API) BrokerSearch(query SearchQueryType) ([]Broker, error) { - queryURL := fmt.Sprintf("/broker?%s", string(query)) - - result, err := a.Get(queryURL) - if err != nil { - return nil, err - } - - var brokers []Broker - if err := json.Unmarshal(result, &brokers); err != nil { - return nil, err - } - - return brokers, nil -} - -// FetchBrokerList return list of all brokers available to the api token/app -func (a *API) FetchBrokerList() ([]Broker, error) { - result, err := a.Get("/broker") +// 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 } @@ -92,5 +87,45 @@ func (a *API) FetchBrokerList() ([]Broker, error) { return nil, err } - return response, nil + 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 } diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/check.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/check.go index 0887caf3d..047d71935 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/api/check.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/check.go @@ -2,42 +2,58 @@ // 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" - "strings" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" ) -// CheckDetails is an arbitrary json structure, we would only care about submission_url -type CheckDetails struct { - SubmissionURL string `json:"submission_url"` -} +// CheckDetails contains [undocumented] check type specific information +type CheckDetails map[config.Key]string -// Check definition +// Check defines a check. See https://login.circonus.com/resources/api/calls/check for more information. type Check struct { - Cid string `json:"_cid"` - Active bool `json:"_active"` - BrokerCid string `json:"_broker"` - CheckBundleCid string `json:"_check_bundle"` - CheckUUID string `json:"_check_uuid"` - Details CheckDetails `json:"_details"` + 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 } -// FetchCheckByID fetch a check configuration by id -func (a *API) FetchCheckByID(id IDType) (*Check, error) { - cid := CIDType(fmt.Sprintf("/check/%d", int(id))) - return a.FetchCheckByCID(cid) -} +// 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]") + } -// FetchCheckByCID fetch a check configuration by cid -func (a *API) FetchCheckByCID(cid CIDType) (*Check, error) { - result, err := a.Get(string(cid)) + 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 { @@ -47,62 +63,49 @@ func (a *API) FetchCheckByCID(cid CIDType) (*Check, error) { return check, nil } -// FetchCheckBySubmissionURL fetch a check configuration by submission_url -func (a *API) FetchCheckBySubmissionURL(submissionURL URLType) (*Check, error) { - - u, err := url.Parse(string(submissionURL)) +// 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 } - // 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 := SearchFilterType(fmt.Sprintf("f__check_uuid=%s", uuid)) - - checks, err := a.CheckFilterSearch(filter) - if err != nil { + var checks []Check + if err := json.Unmarshal(result, &checks); err != nil { return nil, err } - if len(checks) == 0 { - return nil, fmt.Errorf("[ERROR] No checks found with UUID %s", uuid) + 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)) } - numActive := 0 - checkID := -1 - - for idx, check := range checks { - if check.Active { - numActive++ - checkID = idx + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } } } - if numActive > 1 { - return nil, fmt.Errorf("[ERROR] Multiple checks with same UUID %s", uuid) + if q.Encode() == "" { + return a.FetchChecks() } - return &checks[checkID], nil + reqURL := url.URL{ + Path: config.CheckPrefix, + RawQuery: q.Encode(), + } -} - -// CheckSearch returns a list of checks matching a search query -func (a *API) CheckSearch(query SearchQueryType) ([]Check, error) { - queryURL := fmt.Sprintf("/check?search=%s", string(query)) - - result, err := a.Get(queryURL) + result, err := a.Get(reqURL.String()) if err != nil { return nil, err } @@ -112,22 +115,5 @@ func (a *API) CheckSearch(query SearchQueryType) ([]Check, error) { return nil, err } - return checks, nil -} - -// CheckFilterSearch returns a list of checks matching a filter -func (a *API) CheckFilterSearch(filter SearchFilterType) ([]Check, error) { - filterURL := fmt.Sprintf("/check?%s", string(filter)) - - result, err := a.Get(filterURL) - if err != nil { - return nil, err - } - - var checks []Check - if err := json.Unmarshal(result, &checks); err != nil { - return nil, err - } - - return checks, nil + return &checks, nil } diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle.go new file mode 100644 index 000000000..8ab851e0c --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle.go @@ -0,0 +1,255 @@ +// 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,omitempty"` // 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle_metrics.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle_metrics.go new file mode 100644 index 000000000..817c7b891 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle_metrics.go @@ -0,0 +1,95 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/checkbundle.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/checkbundle.go deleted file mode 100644 index e5faae0fb..000000000 --- a/vendor/github.com/circonus-labs/circonus-gometrics/api/checkbundle.go +++ /dev/null @@ -1,139 +0,0 @@ -// 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 ( - "encoding/json" - "fmt" -) - -// CheckBundleConfig configuration specific to check type -type CheckBundleConfig struct { - AsyncMetrics bool `json:"async_metrics"` - Secret string `json:"secret"` - SubmissionURL string `json:"submission_url"` - ReverseSecret string `json:"reverse:secret_key"` - HTTPVersion string `json:"http_version,omitempty"` - Method string `json:"method,omitempty"` - Payload string `json:"payload,omitempty"` - Port string `json:"port,omitempty"` - ReadLimit string `json:"read_limit,omitempty"` - URL string `json:"url,omitempty"` -} - -// CheckBundleMetric individual metric configuration -type CheckBundleMetric struct { - Name string `json:"name"` - Type string `json:"type"` - Units string `json:"units"` - Status string `json:"status"` - Tags []string `json:"tags"` -} - -// CheckBundle definition -type CheckBundle struct { - CheckUUIDs []string `json:"_check_uuids,omitempty"` - Checks []string `json:"_checks,omitempty"` - Cid string `json:"_cid,omitempty"` - Created int `json:"_created,omitempty"` - LastModified int `json:"_last_modified,omitempty"` - LastModifedBy string `json:"_last_modifed_by,omitempty"` - ReverseConnectURLs []string `json:"_reverse_connection_urls"` - Brokers []string `json:"brokers"` - Config CheckBundleConfig `json:"config"` - DisplayName string `json:"display_name"` - Metrics []CheckBundleMetric `json:"metrics"` - MetricLimit int `json:"metric_limit"` - Notes string `json:"notes"` - Period int `json:"period"` - Status string `json:"status"` - Tags []string `json:"tags"` - Target string `json:"target"` - Timeout int `json:"timeout"` - Type string `json:"type"` -} - -// FetchCheckBundleByID fetch a check bundle configuration by id -func (a *API) FetchCheckBundleByID(id IDType) (*CheckBundle, error) { - cid := CIDType(fmt.Sprintf("/check_bundle/%d", id)) - return a.FetchCheckBundleByCID(cid) -} - -// FetchCheckBundleByCID fetch a check bundle configuration by id -func (a *API) FetchCheckBundleByCID(cid CIDType) (*CheckBundle, error) { - result, err := a.Get(string(cid)) - if err != nil { - return nil, err - } - - checkBundle := &CheckBundle{} - if err := json.Unmarshal(result, checkBundle); err != nil { - return nil, err - } - - return checkBundle, nil -} - -// CheckBundleSearch returns list of check bundles matching a search query -// - a search query not a filter (see: https://login.circonus.com/resources/api#searching) -func (a *API) CheckBundleSearch(searchCriteria SearchQueryType) ([]CheckBundle, error) { - apiPath := fmt.Sprintf("/check_bundle?search=%s", searchCriteria) - - response, err := a.Get(apiPath) - if err != nil { - return nil, fmt.Errorf("[ERROR] API call error %+v", err) - } - - var results []CheckBundle - if err := json.Unmarshal(response, &results); err != nil { - return nil, err - } - - return results, nil -} - -// CreateCheckBundle create a new check bundle (check) -func (a *API) CreateCheckBundle(config CheckBundle) (*CheckBundle, error) { - cfgJSON, err := json.Marshal(config) - if err != nil { - return nil, err - } - - response, err := a.Post("/check_bundle", cfgJSON) - if err != nil { - return nil, err - } - - checkBundle := &CheckBundle{} - if err := json.Unmarshal(response, checkBundle); err != nil { - return nil, err - } - - return checkBundle, nil -} - -// UpdateCheckBundle updates a check bundle configuration -func (a *API) UpdateCheckBundle(config *CheckBundle) (*CheckBundle, error) { - if a.Debug { - a.Log.Printf("[DEBUG] Updating check bundle.") - } - - cfgJSON, err := json.Marshal(config) - if err != nil { - return nil, err - } - - response, err := a.Put(config.Cid, cfgJSON) - if err != nil { - return nil, err - } - - checkBundle := &CheckBundle{} - if err := json.Unmarshal(response, checkBundle); err != nil { - return nil, err - } - - return checkBundle, nil -} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/config/consts.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/config/consts.go new file mode 100644 index 000000000..bbca43d03 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/config/consts.go @@ -0,0 +1,538 @@ +package config + +// Key for CheckBundleConfig options and CheckDetails info +type Key string + +// Constants per type as defined in +// https://login.circonus.com/resources/api/calls/check_bundle +const ( + // + // default settings for api.NewCheckBundle() + // + DefaultCheckBundleMetricLimit = -1 // unlimited + DefaultCheckBundleStatus = "active" + DefaultCheckBundlePeriod = 60 + DefaultCheckBundleTimeout = 10 + DefaultConfigOptionsSize = 20 + + // + // common (apply to more than one check type) + // + AsyncMetrics = Key("asynch_metrics") + AuthMethod = Key("auth_method") + AuthPassword = Key("auth_password") + AuthUser = Key("auth_user") + BaseURL = Key("base_url") + CAChain = Key("ca_chain") + CertFile = Key("certificate_file") + Ciphers = Key("ciphers") + Command = Key("command") + DSN = Key("dsn") + HeaderPrefix = Key("header_") + HTTPVersion = Key("http_version") + KeyFile = Key("key_file") + Method = Key("method") + Password = Key("password") + Payload = Key("payload") + Port = Key("port") + Query = Key("query") + ReadLimit = Key("read_limit") + Secret = Key("secret") + SQL = Key("sql") + URI = Key("uri") + URL = Key("url") + Username = Key("username") + UseSSL = Key("use_ssl") + User = Key("user") + SASLAuthentication = Key("sasl_authentication") + SASLUser = Key("sasl_user") + SecurityLevel = Key("security_level") + Version = Key("version") + AppendColumnName = Key("append_column_name") + Database = Key("database") + JDBCPrefix = Key("jdbc_") + + // + // CAQL check + // + // Common items: + // Query + + // + // Circonus Windows Agent + // + // Common items: + // AuthPassword + // AuthUser + // Port + // URL + Calculated = Key("calculated") + Category = Key("category") + + // + // Cloudwatch + // + // Notes: + // DimPrefix is special because the actual key is dynamic and matches: `dim_(.+)` + // Common items: + // URL + // Version + APIKey = Key("api_key") + APISecret = Key("api_secret") + CloudwatchMetrics = Key("cloudwatch_metrics") + DimPrefix = Key("dim_") + Granularity = Key("granularity") + Namespace = Key("namespace") + Statistics = Key("statistics") + + // + // Collectd + // + // Common items: + // AsyncMetrics + // Username + // Secret + // SecurityLevel + + // + // Composite + // + CompositeMetricName = Key("composite_metric_name") + Formula = Key("formula") + + // + // DHCP + // + HardwareAddress = Key("hardware_addr") + HostIP = Key("host_ip") + RequestType = Key("request_type") + SendPort = Key("send_port") + + // + // DNS + // + // Common items: + // Query + CType = Key("ctype") + Nameserver = Key("nameserver") + RType = Key("rtype") + + // + // EC Console + // + // Common items: + // Command + // Port + // SASLAuthentication + // SASLUser + Objects = Key("objects") + XPath = Key("xpath") + + // + // Elastic Search + // + // Common items: + // Port + // URL + + // + // Ganglia + // + // Common items: + // AsyncMetrics + + // + // Google Analytics + // + // Common items: + // Password + // Username + OAuthToken = Key("oauth_token") + OAuthTokenSecret = Key("oauth_token_secret") + OAuthVersion = Key("oauth_version") + TableID = Key("table_id") + UseOAuth = Key("use_oauth") + + // + // HA Proxy + // + // Common items: + // AuthPassword + // AuthUser + // Port + // UseSSL + Host = Key("host") + Select = Key("select") + + // + // HTTP + // + // Notes: + // HeaderPrefix is special because the actual key is dynamic and matches: `header_(\S+)` + // Common items: + // AuthMethod + // AuthPassword + // AuthUser + // CAChain + // CertFile + // Ciphers + // KeyFile + // URL + // HeaderPrefix + // HTTPVersion + // Method + // Payload + // ReadLimit + Body = Key("body") + Code = Key("code") + Extract = Key("extract") + Redirects = Key("redirects") + + // + // HTTPTRAP + // + // Common items: + // AsyncMetrics + // Secret + + // + // IMAP + // + // Common items: + // AuthPassword + // AuthUser + // CAChain + // CertFile + // Ciphers + // KeyFile + // Port + // UseSSL + Fetch = Key("fetch") + Folder = Key("folder") + HeaderHost = Key("header_Host") + Search = Key("search") + + // + // JMX + // + // Common items: + // Password + // Port + // URI + // Username + MbeanDomains = Key("mbean_domains") + + // + // JSON + // + // Common items: + // AuthMethod + // AuthPassword + // AuthUser + // CAChain + // CertFile + // Ciphers + // HeaderPrefix + // HTTPVersion + // KeyFile + // Method + // Payload + // Port + // ReadLimit + // URL + + // + // Keynote + // + // Notes: + // SlotAliasPrefix is special because the actual key is dynamic and matches: `slot_alias_(\d+)` + // Common items: + // APIKey + // BaseURL + PageComponent = Key("pagecomponent") + SlotAliasPrefix = Key("slot_alias_") + SlotIDList = Key("slot_id_list") + TransPageList = Key("transpagelist") + + // + // Keynote Pulse + // + // Common items: + // BaseURL + // Password + // User + AgreementID = Key("agreement_id") + + // + // LDAP + // + // Common items: + // Password + // Port + AuthType = Key("authtype") + DN = Key("dn") + SecurityPrincipal = Key("security_principal") + + // + // Memcached + // + // Common items: + // Port + + // + // MongoDB + // + // Common items: + // Command + // Password + // Port + // Username + DBName = Key("dbname") + + // + // Munin + // + // Note: no configuration options + + // + // MySQL + // + // Common items: + // DSN + // SQL + + // + // Newrelic rpm + // + // Common items: + // APIKey + AccountID = Key("acct_id") + ApplicationID = Key("application_id") + LicenseKey = Key("license_key") + + // + // Nginx + // + // Common items: + // CAChain + // CertFile + // Ciphers + // KeyFile + // URL + + // + // NRPE + // + // Common items: + // Command + // Port + // UseSSL + AppendUnits = Key("append_uom") + + // + // NTP + // + // Common items: + // Port + Control = Key("control") + + // + // Oracle + // + // Notes: + // JDBCPrefix is special because the actual key is dynamic and matches: `jdbc_(\S+)` + // Common items: + // AppendColumnName + // Database + // JDBCPrefix + // Password + // Port + // SQL + // User + + // + // Ping ICMP + // + AvailNeeded = Key("avail_needed") + Count = Key("count") + Interval = Key("interval") + + // + // PostgreSQL + // + // Common items: + // DSN + // SQL + + // + // Redis + // + // Common items: + // Command + // Password + // Port + DBIndex = Key("dbindex") + + // + // Resmon + // + // Notes: + // HeaderPrefix is special because the actual key is dynamic and matches: `header_(\S+)` + // Common items: + // AuthMethod + // AuthPassword + // AuthUser + // CAChain + // CertFile + // Ciphers + // HeaderPrefix + // HTTPVersion + // KeyFile + // Method + // Payload + // Port + // ReadLimit + // URL + + // + // SMTP + // + // Common items: + // Payload + // Port + // SASLAuthentication + // SASLUser + EHLO = Key("ehlo") + From = Key("from") + SASLAuthID = Key("sasl_auth_id") + SASLPassword = Key("sasl_password") + StartTLS = Key("starttls") + To = Key("to") + + // + // SNMP + // + // Notes: + // OIDPrefix is special because the actual key is dynamic and matches: `oid_(.+)` + // TypePrefix is special because the actual key is dynamic and matches: `type_(.+)` + // Common items: + // Port + // SecurityLevel + // Version + AuthPassphrase = Key("auth_passphrase") + AuthProtocol = Key("auth_protocol") + Community = Key("community") + ContextEngine = Key("context_engine") + ContextName = Key("context_name") + OIDPrefix = Key("oid_") + PrivacyPassphrase = Key("privacy_passphrase") + PrivacyProtocol = Key("privacy_protocol") + SecurityEngine = Key("security_engine") + SecurityName = Key("security_name") + SeparateQueries = Key("separate_queries") + TypePrefix = Key("type_") + + // + // SQLServer + // + // Notes: + // JDBCPrefix is special because the actual key is dynamic and matches: `jdbc_(\S+)` + // Common items: + // AppendColumnName + // Database + // JDBCPrefix + // Password + // Port + // SQL + // User + + // + // SSH v2 + // + // Common items: + // Port + MethodCompCS = Key("method_comp_cs") + MethodCompSC = Key("method_comp_sc") + MethodCryptCS = Key("method_crypt_cs") + MethodCryptSC = Key("method_crypt_sc") + MethodHostKey = Key("method_hostkey") + MethodKeyExchange = Key("method_kex") + MethodMacCS = Key("method_mac_cs") + MethodMacSC = Key("method_mac_sc") + + // + // StatsD + // + // Note: no configuration options + + // + // TCP + // + // Common items: + // CAChain + // CertFile + // Ciphers + // KeyFile + // Port + // UseSSL + BannerMatch = Key("banner_match") + + // + // Varnish + // + // Note: no configuration options + + // + // reserved - config option(s) can't actually be set - here for r/o access + // + ReverseSecretKey = Key("reverse:secret_key") + SubmissionURL = Key("submission_url") + + // + // Endpoint prefix & cid regex + // + DefaultCIDRegex = "[0-9]+" + DefaultUUIDRegex = "[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}" + AccountPrefix = "/account" + AccountCIDRegex = "^(" + AccountPrefix + "/(" + DefaultCIDRegex + "|current))$" + AcknowledgementPrefix = "/acknowledgement" + AcknowledgementCIDRegex = "^(" + AcknowledgementPrefix + "/(" + DefaultCIDRegex + "))$" + AlertPrefix = "/alert" + AlertCIDRegex = "^(" + AlertPrefix + "/(" + DefaultCIDRegex + "))$" + AnnotationPrefix = "/annotation" + AnnotationCIDRegex = "^(" + AnnotationPrefix + "/(" + DefaultCIDRegex + "))$" + BrokerPrefix = "/broker" + BrokerCIDRegex = "^(" + BrokerPrefix + "/(" + DefaultCIDRegex + "))$" + CheckBundleMetricsPrefix = "/check_bundle_metrics" + CheckBundleMetricsCIDRegex = "^(" + CheckBundleMetricsPrefix + "/(" + DefaultCIDRegex + "))$" + CheckBundlePrefix = "/check_bundle" + CheckBundleCIDRegex = "^(" + CheckBundlePrefix + "/(" + DefaultCIDRegex + "))$" + CheckPrefix = "/check" + CheckCIDRegex = "^(" + CheckPrefix + "/(" + DefaultCIDRegex + "))$" + ContactGroupPrefix = "/contact_group" + ContactGroupCIDRegex = "^(" + ContactGroupPrefix + "/(" + DefaultCIDRegex + "))$" + DashboardPrefix = "/dashboard" + DashboardCIDRegex = "^(" + DashboardPrefix + "/(" + DefaultCIDRegex + "))$" + GraphPrefix = "/graph" + GraphCIDRegex = "^(" + GraphPrefix + "/(" + DefaultUUIDRegex + "))$" + MaintenancePrefix = "/maintenance" + MaintenanceCIDRegex = "^(" + MaintenancePrefix + "/(" + DefaultCIDRegex + "))$" + MetricClusterPrefix = "/metric_cluster" + MetricClusterCIDRegex = "^(" + MetricClusterPrefix + "/(" + DefaultCIDRegex + "))$" + MetricPrefix = "/metric" + MetricCIDRegex = "^(" + MetricPrefix + "/((" + DefaultCIDRegex + ")_([^[:space:]]+)))$" + OutlierReportPrefix = "/outlier_report" + OutlierReportCIDRegex = "^(" + OutlierReportPrefix + "/(" + DefaultCIDRegex + "))$" + ProvisionBrokerPrefix = "/provision_broker" + ProvisionBrokerCIDRegex = "^(" + ProvisionBrokerPrefix + "/([a-z0-9]+-[a-z0-9]+))$" + RuleSetGroupPrefix = "/rule_set_group" + RuleSetGroupCIDRegex = "^(" + RuleSetGroupPrefix + "/(" + DefaultCIDRegex + "))$" + RuleSetPrefix = "/rule_set" + RuleSetCIDRegex = "^(" + RuleSetPrefix + "/((" + DefaultCIDRegex + ")_([^[:space:]]+)))$" + UserPrefix = "/user" + UserCIDRegex = "^(" + UserPrefix + "/(" + DefaultCIDRegex + "|current))$" + WorksheetPrefix = "/worksheet" + WorksheetCIDRegex = "^(" + WorksheetPrefix + "/(" + DefaultUUIDRegex + "))$" + // contact group serverity levels + NumSeverityLevels = 5 +) diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/contact_group.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/contact_group.go new file mode 100644 index 000000000..578a2e898 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/contact_group.go @@ -0,0 +1,263 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/dashboard-example.json b/vendor/github.com/circonus-labs/circonus-gometrics/api/dashboard-example.json new file mode 100644 index 000000000..627639e74 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/dashboard-example.json @@ -0,0 +1,390 @@ +{ + "_active": true, + "_cid": "/dashboard/1234", + "_created": 1483193930, + "_created_by": "/user/1234", + "_dashboard_uuid": "01234567-89ab-cdef-0123-456789abcdef", + "_last_modified": 1483450351, + "account_default": false, + "grid_layout": { + "height": 4, + "width": 4 + }, + "options": { + "access_configs": [ + ], + "fullscreen_hide_title": false, + "hide_grid": false, + "linkages": [ + ], + "scale_text": true, + "text_size": 16 + }, + "shared": false, + "title": "foo bar baz", + "widgets": [ + { + "active": true, + "height": 1, + "name": "Cluster", + "origin": "d0", + "settings": { + "account_id": "1234", + "algorithm": "cor", + "cluster_id": 1234, + "cluster_name": "test", + "layout": "compact", + "size": "medium", + "threshold": 0.7 + }, + "type": "cluster", + "widget_id": "w4", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "HTML", + "origin": "d1", + "settings": { + "markup": "

foo

", + "title": "html" + }, + "type": "html", + "widget_id": "w9", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "Chart", + "origin": "c0", + "settings": { + "chart_type": "bar", + "datapoints": [ + { + "_check_id": 1234, + "_metric_type": "numeric", + "account_id": "1234", + "label": "Used", + "metric": "01234567-89ab-cdef-0123-456789abcdef:vm`memory`used" + }, + { + "_check_id": 1234, + "_metric_type": "numeric", + "account_id": "1234", + "label": "Free", + "metric": "01234567-89ab-cdef-0123-456789abcdef:vm`memory`free" + } + ], + "definition": { + "datasource": "realtime", + "derive": "gauge", + "disable_autoformat": false, + "formula": "", + "legend": { + "show": false, + "type": "html" + }, + "period": 0, + "pop_onhover": false, + "wedge_labels": { + "on_chart": true, + "tooltips": false + }, + "wedge_values": { + "angle": "0", + "color": "background", + "show": true + } + }, + "title": "chart graph" + }, + "type": "chart", + "widget_id": "w5", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "Alerts", + "origin": "a0", + "settings": { + "account_id": "1234", + "acknowledged": "all", + "cleared": "all", + "contact_groups": [ + ], + "dependents": "all", + "display": "list", + "maintenance": "all", + "min_age": "0", + "off_hours": [ + 17, + 9 + ], + "search": "", + "severity": "12345", + "tag_filter_set": [ + ], + "time_window": "30M", + "title": "alerts", + "week_days": [ + "sun", + "mon", + "tue", + "wed", + "thu", + "fri", + "sat" + ] + }, + "type": "alerts", + "widget_id": "w2", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "Graph", + "origin": "c1", + "settings": { + "_graph_title": "foo bar / %Used", + "account_id": "1234", + "date_window": "2w", + "graph_id": "01234567-89ab-cdef-0123-456789abcdef", + "hide_xaxis": false, + "hide_yaxis": false, + "key_inline": false, + "key_loc": "noop", + "key_size": "1", + "key_wrap": false, + "label": "", + "overlay_set_id": "", + "period": "2000", + "previous_graph_id": "null", + "realtime": false, + "show_flags": false + }, + "type": "graph", + "widget_id": "w8", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "List", + "origin": "a2", + "settings": { + "account_id": "1234", + "limit": "10", + "search": "", + "type": "graph" + }, + "type": "list", + "widget_id": "w10", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "Status", + "origin": "b2", + "settings": { + "account_id": "1234", + "agent_status_settings": { + "search": "", + "show_agent_types": "both", + "show_contact": false, + "show_feeds": true, + "show_setup": false, + "show_skew": true, + "show_updates": true + }, + "content_type": "agent_status", + "host_status_settings": { + "layout_style": "grid", + "search": "", + "sort_by": "alerts", + "tag_filter_set": [ + ] + } + }, + "type": "status", + "widget_id": "w11", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "Text", + "origin": "d2", + "settings": { + "autoformat": false, + "body_format": "

{metric_name} ({value_type})
{metric_value}
{value_date}

", + "datapoints": [ + { + "_cluster_title": "test", + "_label": "Cluster: test", + "account_id": "1234", + "cluster_id": 1234, + "numeric_only": false + } + ], + "period": 0, + "title_format": "Metric Status", + "use_default": true, + "value_type": "gauge" + }, + "type": "text", + "widget_id": "w13", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "Chart", + "origin": "b0", + "settings": { + "chart_type": "bar", + "datapoints": [ + { + "_cluster_title": "test", + "_label": "Cluster: test", + "account_id": "1234", + "cluster_id": 1234, + "numeric_only": true + } + ], + "definition": { + "datasource": "realtime", + "derive": "gauge", + "disable_autoformat": false, + "formula": "", + "legend": { + "show": false, + "type": "html" + }, + "period": 0, + "pop_onhover": false, + "wedge_labels": { + "on_chart": true, + "tooltips": false + }, + "wedge_values": { + "angle": "0", + "color": "background", + "show": true + } + }, + "title": "chart metric cluster" + }, + "type": "chart", + "widget_id": "w3", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "Gauge", + "origin": "b1", + "settings": { + "_check_id": 1234, + "account_id": "1234", + "check_uuid": "01234567-89ab-cdef-0123-456789abcdef", + "disable_autoformat": false, + "formula": "", + "metric_display_name": "%Used", + "metric_name": "fs`/foo`df_used_percent", + "period": 0, + "range_high": 100, + "range_low": 0, + "thresholds": { + "colors": [ + "#008000", + "#ffcc00", + "#ee0000" + ], + "flip": false, + "values": [ + "75%", + "87.5%" + ] + }, + "title": "Metric Gauge", + "type": "bar", + "value_type": "gauge" + }, + "type": "gauge", + "widget_id": "w7", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "Text", + "origin": "c2", + "settings": { + "autoformat": false, + "body_format": "

{metric_name} ({value_type})
{metric_value}
{value_date}

", + "datapoints": [ + { + "_check_id": 1234, + "_metric_type": "numeric", + "account_id": "1234", + "label": "cache entries", + "metric": "01234567-89ab-cdef-0123-456789abcdef:foo`cache_entries" + }, + { + "_check_id": 1234, + "_metric_type": "numeric", + "account_id": "1234", + "label": "cache capacity", + "metric": "01234567-89ab-cdef-0123-456789abcdef:foo`cache_capacity" + }, + { + "_check_id": 1234, + "_metric_type": "numeric", + "account_id": "1234", + "label": "cache size", + "metric": "01234567-89ab-cdef-0123-456789abcdef:foo`cache_size" + } + ], + "period": 0, + "title_format": "Metric Status", + "use_default": true, + "value_type": "gauge" + }, + "type": "text", + "widget_id": "w12", + "width": 1 + }, + { + "active": true, + "height": 1, + "name": "Forecast", + "origin": "a1", + "settings": { + "format": "standard", + "resource_limit": "0", + "resource_usage": "metric:average(\"01234567-89ab-cdef-0123-456789abcdef\",p\"fs%60/foo%60df_used_percent\")", + "thresholds": { + "colors": [ + "#008000", + "#ffcc00", + "#ee0000" + ], + "values": [ + "1d", + "1h" + ] + }, + "title": "Resource Forecast", + "trend": "auto" + }, + "type": "forecast", + "widget_id": "w6", + "width": 1 + } + ] +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/dashboard.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/dashboard.go new file mode 100644 index 000000000..5bca0a3cd --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/dashboard.go @@ -0,0 +1,399 @@ +// 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 string `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 string `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 interface{} `json:"period,omitempty"` // BUG type switching between widgets (doc: string; gauges, text: uint; graphs: string) + 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/doc.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/doc.go new file mode 100644 index 000000000..63904d784 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/doc.go @@ -0,0 +1,63 @@ +// 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 provides methods for interacting with the Circonus API. See the full Circonus API +Documentation at https://login.circonus.com/resources/api for more information. + +Raw REST methods + + Get - retrieve existing item(s) + Put - update an existing item + Post - create a new item + Delete - remove an existing item + +Endpoints (supported) + + Account https://login.circonus.com/resources/api/calls/account + Acknowledgement https://login.circonus.com/resources/api/calls/acknowledgement + Alert https://login.circonus.com/resources/api/calls/alert + Annotation https://login.circonus.com/resources/api/calls/annotation + Broker https://login.circonus.com/resources/api/calls/broker + Check https://login.circonus.com/resources/api/calls/check + Check Bundle https://login.circonus.com/resources/api/calls/check_bundle + Check Bundle Metrics https://login.circonus.com/resources/api/calls/check_bundle_metrics + Contact Group https://login.circonus.com/resources/api/calls/contact_group + Dashboard https://login.circonus.com/resources/api/calls/dashboard + Graph https://login.circonus.com/resources/api/calls/graph + Maintenance [window] https://login.circonus.com/resources/api/calls/maintenance + Metric https://login.circonus.com/resources/api/calls/metric + Metric Cluster https://login.circonus.com/resources/api/calls/metric_cluster + Outlier Report https://login.circonus.com/resources/api/calls/outlier_report + Provision Broker https://login.circonus.com/resources/api/calls/provision_broker + Rule Set https://login.circonus.com/resources/api/calls/rule_set + Rule Set Group https://login.circonus.com/resources/api/calls/rule_set_group + User https://login.circonus.com/resources/api/calls/user + Worksheet https://login.circonus.com/resources/api/calls/worksheet + +Endpoints (not supported) + + Support may be added for these endpoints in the future. These endpoints may currently be used + directly with the Raw REST methods above. + + CAQL https://login.circonus.com/resources/api/calls/caql + Check Move https://login.circonus.com/resources/api/calls/check_move + Data https://login.circonus.com/resources/api/calls/data + Snapshot https://login.circonus.com/resources/api/calls/snapshot + Tag https://login.circonus.com/resources/api/calls/tag + Template https://login.circonus.com/resources/api/calls/template + +Verbs + + Fetch singular/plural item(s) - e.g. FetchAnnotation, FetchAnnotations + Create create new item - e.g. CreateAnnotation + Update update an item - e.g. UpdateAnnotation + Delete remove an item - e.g. DeleteAnnotation, DeleteAnnotationByCID + Search search for item(s) - e.g. SearchAnnotations + New new item config - e.g. NewAnnotation (returns an empty item, + any applicable defautls defined) + + Not all endpoints support all verbs. +*/ +package api diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/graph.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/graph.go new file mode 100644 index 000000000..ab98e975c --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/graph.go @@ -0,0 +1,348 @@ +// 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 + 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,omitempty"` // string + 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,omitempty"` // string + 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/maintenance.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/maintenance.go new file mode 100644 index 000000000..0e5e04729 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/maintenance.go @@ -0,0 +1,220 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/metric.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/metric.go new file mode 100644 index 000000000..a77d41ce5 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/metric.go @@ -0,0 +1,162 @@ +// 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 bool `json:"_histogram,omitempty"` // boolean + 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/metric_cluster.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/metric_cluster.go new file mode 100644 index 000000000..d29c5a674 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/metric_cluster.go @@ -0,0 +1,261 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/outlier_report.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/outlier_report.go new file mode 100644 index 000000000..bc1a4d2b3 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/outlier_report.go @@ -0,0 +1,221 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/provision_broker.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/provision_broker.go new file mode 100644 index 000000000..5b432a236 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/provision_broker.go @@ -0,0 +1,151 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set.go new file mode 100644 index 000000000..3da0907f7 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set.go @@ -0,0 +1,234 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set_group.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set_group.go new file mode 100644 index 000000000..a15743061 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set_group.go @@ -0,0 +1,231 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/user.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/user.go new file mode 100644 index 000000000..7771991d3 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/user.go @@ -0,0 +1,159 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/worksheet.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/worksheet.go new file mode 100644 index 000000000..0dd5e9373 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/worksheet.go @@ -0,0 +1,232 @@ +// 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 +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/broker.go b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/broker.go index 78fff7606..caeaaef33 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/broker.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/broker.go @@ -24,7 +24,8 @@ func init() { // Get Broker to use when creating a check func (cm *CheckManager) getBroker() (*api.Broker, error) { if cm.brokerID != 0 { - broker, err := cm.apih.FetchBrokerByID(cm.brokerID) + cid := fmt.Sprintf("/broker/%d", cm.brokerID) + broker, err := cm.apih.FetchBroker(api.CIDType(&cid)) if err != nil { return nil, err } @@ -60,7 +61,7 @@ func (cm *CheckManager) getBrokerCN(broker *api.Broker, submissionURL api.URLTyp cn := "" for _, detail := range broker.Details { - if detail.IP == host { + if *detail.IP == host { cn = detail.CN break } @@ -77,31 +78,34 @@ func (cm *CheckManager) getBrokerCN(broker *api.Broker, submissionURL api.URLTyp // 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 brokerList *[]api.Broker var err error if len(cm.brokerSelectTag) > 0 { - brokerList, err = cm.apih.FetchBrokerListByTag(cm.brokerSelectTag) + 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.FetchBrokerList() + brokerList, err = cm.apih.FetchBrokers() if err != nil { return nil, err } } - if len(brokerList) == 0 { + if len(*brokerList) == 0 { return nil, fmt.Errorf("zero brokers found") } validBrokers := make(map[string]api.Broker) haveEnterprise := false - for _, broker := range brokerList { + for _, broker := range *brokerList { if cm.isValidBroker(&broker) { - validBrokers[broker.Cid] = broker + validBrokers[broker.CID] = broker if broker.Type == "enterprise" { haveEnterprise = true } @@ -117,7 +121,7 @@ func (cm *CheckManager) selectBroker() (*api.Broker, error) { } if len(validBrokers) == 0 { - return nil, fmt.Errorf("found %d broker(s), zero are valid", len(brokerList)) + return nil, fmt.Errorf("found %d broker(s), zero are valid", len(*brokerList)) } validBrokerKeys := reflect.ValueOf(validBrokers).MapKeys() @@ -146,8 +150,8 @@ func (cm *CheckManager) brokerSupportsCheckType(checkType CheckTypeType, details // Is the broker valid (active, supports check type, and reachable) func (cm *CheckManager) isValidBroker(broker *api.Broker) bool { - brokerHost := "" - brokerPort := "" + var brokerHost string + var brokerPort string valid := false for _, detail := range broker.Details { @@ -168,49 +172,45 @@ func (cm *CheckManager) isValidBroker(broker *api.Broker) bool { } if detail.ExternalPort != 0 { - brokerPort = strconv.Itoa(detail.ExternalPort) + brokerPort = strconv.Itoa(int(detail.ExternalPort)) } else { - if detail.Port != 0 { - brokerPort = strconv.Itoa(detail.Port) + if *detail.Port != 0 { + brokerPort = strconv.Itoa(int(*detail.Port)) } else { brokerPort = "43191" } } - if detail.ExternalHost != "" { - brokerHost = detail.ExternalHost + if detail.ExternalHost != nil && *detail.ExternalHost != "" { + brokerHost = *detail.ExternalHost } else { - brokerHost = detail.IP + brokerHost = *detail.IP } - // 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 { - if detail.CN != "trap.noit.circonus.net" { - if cm.Debug { - cm.Log.Printf("[DEBUG] Broker '%s' unable to connect, %v\n", broker.Name, err) - } - continue // not able to reach the broker (or respone slow enough for it to be considered not usable) - } - // if circonus trap broker, try port 443 + if brokerHost == "trap.noit.circonus.net" && brokerPort != "443" { brokerPort = "443" - conn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%s", detail.CN, brokerPort), cm.brokerMaxResponseTime) - if err != nil { - if cm.Debug { - cm.Log.Printf("[DEBUG] Broker '%s' unable to connect %v\n", broker.Name, err) - } - continue // not able to reach the broker on 443 either (or respone slow enough for it to be considered not usable) + } + + 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 } - } - conn.Close() - if cm.Debug { - cm.Log.Printf("[DEBUG] Broker '%s' is valid\n", broker.Name) + 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) } - valid = true - break - + if valid { + if cm.Debug { + cm.Log.Printf("[DEBUG] Broker '%s' is valid\n", broker.Name) + } + break + } } return valid } diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/cert.go b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/cert.go index c10ffd12b..01f65917f 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/cert.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/cert.go @@ -7,6 +7,7 @@ package checkmgr import ( "crypto/x509" "encoding/json" + "errors" "fmt" ) @@ -65,7 +66,7 @@ func (cm *CheckManager) loadCACert() { // fetchCert fetches CA certificate using Circonus API func (cm *CheckManager) fetchCert() ([]byte, error) { if !cm.enabled { - return circonusCA, nil + return nil, errors.New("check manager is not enabled") } response, err := cm.apih.Get("/pki/ca.crt") diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/check.go b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/check.go index 201ef1e0c..fd31c6d25 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/check.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/check.go @@ -10,11 +10,13 @@ import ( "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.) @@ -35,7 +37,8 @@ func (cm *CheckManager) UpdateCheck(newMetrics map[string]*api.CheckBundleMetric } // refresh check bundle (in case there were changes made by other apps or in UI) - checkBundle, err := cm.apih.FetchCheckBundleByCID(api.CIDType(cm.checkBundle.Cid)) + 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 @@ -44,6 +47,8 @@ func (cm *CheckManager) UpdateCheck(newMetrics map[string]*api.CheckBundleMetric 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 { @@ -105,7 +110,7 @@ func (cm *CheckManager) initializeTrapURL() error { } if !cm.enabled { - return errors.New("Unable to initialize trap, check manager is disabled.") + return errors.New("unable to initialize trap, check manager is disabled") } var err error @@ -114,12 +119,12 @@ func (cm *CheckManager) initializeTrapURL() error { var broker *api.Broker if cm.checkSubmissionURL != "" { - check, err = cm.apih.FetchCheckBySubmissionURL(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) + 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 @@ -128,30 +133,44 @@ func (cm *CheckManager) initializeTrapURL() error { // 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)) + 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) + check.CID, err) } } else if cm.checkID > 0 { - check, err = cm.apih.FetchCheckByID(cm.checkID) + 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) + return fmt.Errorf("[ERROR] Check ID %v is not active", check.CID) } } else { - searchCriteria := fmt.Sprintf( - "(active:1)(host:\"%s\")(type:\"%s\")(tags:%s)(notes:%s)", - cm.checkTarget, cm.checkType, strings.Join(cm.checkSearchTag, ","), fmt.Sprintf("cgm_instanceid=%s", cm.checkInstanceID)) - checkBundle, err = cm.checkBundleSearch(searchCriteria) - if err != nil { - return err + 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 { @@ -166,7 +185,8 @@ func (cm *CheckManager) initializeTrapURL() error { if checkBundle == nil { if check != nil { - checkBundle, err = cm.apih.FetchCheckBundleByCID(api.CIDType(check.CheckBundleCid)) + cid := check.CheckBundleCID + checkBundle, err = cm.apih.FetchCheckBundle(api.CIDType(&cid)) if err != nil { return err } @@ -176,7 +196,8 @@ func (cm *CheckManager) initializeTrapURL() error { } if broker == nil { - broker, err = cm.apih.FetchBrokerByCID(api.CIDType(checkBundle.Brokers[0])) + cid := checkBundle.Brokers[0] + broker, err = cm.apih.FetchBroker(api.CIDType(&cid)) if err != nil { return err } @@ -188,7 +209,14 @@ func (cm *CheckManager) initializeTrapURL() error { // determine the trap url to which metrics should be PUT if checkBundle.Type == "httptrap" { - cm.trapURL = api.URLType(checkBundle.Config.SubmissionURL) + 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 { @@ -197,7 +225,14 @@ func (cm *CheckManager) initializeTrapURL() error { mtevURL := checkBundle.ReverseConnectURLs[0] mtevURL = strings.Replace(mtevURL, "mtev_reverse", "https", 1) mtevURL = strings.Replace(mtevURL, "check", "module/httptrap", 1) - cm.trapURL = api.URLType(fmt.Sprintf("%s/%s", mtevURL, checkBundle.Config.ReverseSecret)) + 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 @@ -214,20 +249,21 @@ func (cm *CheckManager) initializeTrapURL() error { } // Search for a check bundle given a predetermined set of criteria -func (cm *CheckManager) checkBundleSearch(criteria string) (*api.CheckBundle, error) { - checkBundles, err := cm.apih.CheckBundleSearch(api.SearchQueryType(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 { + if len(*checkBundles) == 0 { return nil, nil // trigger creation of a new check } numActive := 0 checkID := -1 - for idx, check := range checkBundles { + for idx, check := range *checkBundles { if check.Status == statusActive { numActive++ checkID = idx @@ -235,10 +271,12 @@ func (cm *CheckManager) checkBundleSearch(criteria string) (*api.CheckBundle, er } if numActive > 1 { - return nil, fmt.Errorf("[ERROR] Multiple possibilities multiple check bundles match criteria %s\n", criteria) + return nil, fmt.Errorf("[ERROR] multiple check bundles match criteria %s", criteria) } - return &checkBundles[checkID], nil + bundle := (*checkBundles)[checkID] + + return &bundle, nil } // Create a new check to receive metrics @@ -257,17 +295,20 @@ func (cm *CheckManager) createNewCheck() (*api.CheckBundle, *api.Broker, error) return nil, nil, err } - config := api.CheckBundle{ - Brokers: []string{broker.Cid}, - Config: api.CheckBundleConfig{AsyncMetrics: true, Secret: checkSecret}, + config := &api.CheckBundle{ + Brokers: []string{broker.CID}, + Config: map[config.Key]string{ + config.AsyncMetrics: "true", + config.Secret: checkSecret, + }, DisplayName: string(cm.checkDisplayName), Metrics: []api.CheckBundleMetric{}, - MetricLimit: 0, - Notes: fmt.Sprintf("cgm_instanceid=%s", cm.checkInstanceID), + MetricLimit: config.DefaultCheckBundleMetricLimit, + Notes: cm.getNotes(), Period: 60, Status: statusActive, Tags: append(cm.checkSearchTag, cm.checkTags...), - Target: cm.checkTarget, + Target: string(cm.checkTarget), Timeout: 10, Type: string(cm.checkType), } @@ -290,3 +331,64 @@ func (cm *CheckManager) makeSecret() (string, error) { 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 + +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/checkmgr.go b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/checkmgr.go index c44daccc0..f78da390f 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/checkmgr.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/checkmgr.go @@ -59,12 +59,15 @@ type CheckConfig struct { // 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 - // a custom display name for the check (as viewed in UI Checks) - DisplayName string // httptrap check secret (for creating a check) Secret string // additional tags to add to a check (when creating a check) @@ -115,6 +118,9 @@ type CheckTypeType string // CheckInstanceIDType check instance id type CheckInstanceIDType string +// CheckTargetType check target/host +type CheckTargetType string + // CheckSecretType check secret type CheckSecretType string @@ -134,11 +140,14 @@ type CheckManager struct { Debug bool apih *api.API + initialized bool + initializedmu sync.RWMutex + // check checkType CheckTypeType checkID api.IDType checkInstanceID CheckInstanceIDType - checkTarget string + checkTarget CheckTargetType checkSearchTag api.TagType checkSecret CheckSecretType checkTags api.TagType @@ -157,15 +166,16 @@ type CheckManager struct { brokerMaxResponseTime time.Duration // state - checkBundle *api.CheckBundle - cbmu sync.Mutex - availableMetrics map[string]bool - trapURL api.URLType - trapCN BrokerCNType - trapLastUpdate time.Time - trapMaxURLAge time.Duration - trapmu sync.Mutex - certPool *x509.CertPool + 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 @@ -176,15 +186,19 @@ type Trap struct { // 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).") + return nil, errors.New("invalid Check Manager configuration (nil)") } - cm := &CheckManager{ - enabled: false, - } + 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 { @@ -197,34 +211,28 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { if cfg.Check.SubmissionURL != "" { cm.checkSubmissionURL = api.URLType(cfg.Check.SubmissionURL) } + // Blank API Token *disables* check management if cfg.API.TokenKey == "" { - if cm.checkSubmissionURL == "" { - return nil, errors.New("Invalid check manager configuration (no API token AND no submission url).") - } - if err := cm.initializeTrapURL(); err != nil { + 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 } - return cm, nil + cm.apih = apih } - // enable check manager - - cm.enabled = true - - // initialize api handle - - cfg.API.Debug = cm.Debug - cfg.API.Log = cm.Log - - apih, err := api.NewAPI(&cfg.API) - if err != nil { - return nil, err - } - cm.apih = apih - // initialize check related data - cm.checkType = defaultCheckType idSetting := "0" @@ -238,6 +246,7 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { 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) @@ -259,7 +268,12 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { if cm.checkInstanceID == "" { cm.checkInstanceID = CheckInstanceIDType(fmt.Sprintf("%s:%s", hn, an)) } - cm.checkTarget = hn + 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)} @@ -271,10 +285,6 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { cm.checkTags = strings.Split(strings.Replace(cfg.Check.Tags, " ", "", -1), ",") } - if cm.checkDisplayName == "" { - cm.checkDisplayName = CheckDisplayNameType(fmt.Sprintf("%s", string(cm.checkInstanceID))) - } - dur := cfg.Check.MaxURLAge if dur == "" { dur = defaultTrapMaxURLAge @@ -286,7 +296,6 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { cm.trapMaxURLAge = maxDur // setup broker - idSetting = "0" if cfg.Broker.ID != "" { idSetting = cfg.Broker.ID @@ -315,19 +324,54 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { cm.availableMetrics = make(map[string]bool) cm.metricTags = make(map[string][]string) - if err := cm.initializeTrapURL(); err != nil { - return nil, err - } - return cm, nil } -// GetTrap return the trap url -func (cm *CheckManager) GetTrap() (*Trap, error) { - if cm.trapURL == "" { - if err := cm.initializeTrapURL(); err != nil { - return nil, err +// 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{} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/metrics.go b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/metrics.go index 49b7c9457..eb8603a84 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/metrics.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/metrics.go @@ -10,12 +10,18 @@ import ( // 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 { @@ -33,41 +39,57 @@ func (cm *CheckManager) ActivateMetric(name string) bool { func (cm *CheckManager) AddMetricTags(metricName string, tags []string, appendTags bool) bool { tagsUpdated := false - if len(tags) == 0 { + if appendTags && len(tags) == 0 { return tagsUpdated } - if _, exists := cm.metricTags[metricName]; !exists { + currentTags, exists := cm.metricTags[metricName] + if !exists { foundMetric := false - for _, metric := range cm.checkBundle.Metrics { - if metric.Name == metricName { - foundMetric = true - cm.metricTags[metricName] = metric.Tags - break + if cm.checkBundle != nil { + for _, metric := range cm.checkBundle.Metrics { + if metric.Name == metricName { + foundMetric = true + currentTags = metric.Tags + break + } } } if !foundMetric { - cm.metricTags[metricName] = []string{} + currentTags = []string{} } } - action := "no new" + action := "" if appendTags { - numNewTags := countNewTags(cm.metricTags[metricName], tags) + numNewTags := countNewTags(currentTags, tags) if numNewTags > 0 { action = "Added" - cm.metricTags[metricName] = append(cm.metricTags[metricName], tags...) + currentTags = append(currentTags, tags...) tagsUpdated = true } } else { - action = "Set" - cm.metricTags[metricName] = tags - tagsUpdated = true + 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 cm.Debug { + if tagsUpdated { + cm.metricTags[metricName] = currentTags + } + + if cm.Debug && action != "" { cm.Log.Printf("[DEBUG] %s metric tag(s) %s %v\n", action, metricName, tags) } @@ -116,7 +138,9 @@ func (cm *CheckManager) inventoryMetrics() { 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 diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/circonus-gometrics.go b/vendor/github.com/circonus-labs/circonus-gometrics/circonus-gometrics.go index eb15f3aaf..32cae5bb4 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/circonus-gometrics.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/circonus-gometrics.go @@ -58,14 +58,16 @@ type Config struct { // API, Check and Broker configuration options CheckManager checkmgr.Config - // how frequenly to submit metrics to Circonus, default 10 seconds + // 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 + Log *log.Logger + Debug bool + resetCounters bool resetGauges bool resetHistograms bool @@ -99,9 +101,14 @@ type CirconusMetrics struct { // 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).") + return nil, errors.New("invalid configuration (nil)") } cm := &CirconusMetrics{ @@ -114,83 +121,107 @@ func NewCirconusMetrics(cfg *Config) (*CirconusMetrics, error) { textFuncs: make(map[string]func() string), } - cm.Debug = cfg.Debug - cm.Log = cfg.Log + // Logging + { + cm.Debug = cfg.Debug + cm.Log = cfg.Log - if cm.Debug && cfg.Log == nil { - cm.Log = log.New(os.Stderr, "", log.LstdFlags) - } - if cm.Log == nil { - cm.Log = log.New(ioutil.Discard, "", log.LstdFlags) + 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) + } } - fi := defaultFlushInterval - if cfg.Interval != "" { - fi = cfg.Interval + // Flush Interval + { + fi := defaultFlushInterval + if cfg.Interval != "" { + fi = cfg.Interval + } + + dur, err := time.ParseDuration(fi) + if err != nil { + return nil, err + } + cm.flushInterval = dur } - dur, err := time.ParseDuration(fi) - if err != nil { - return nil, err - } - cm.flushInterval = dur - - var setting bool + // metric resets cm.resetCounters = true if cfg.ResetCounters != "" { - if setting, err = strconv.ParseBool(cfg.ResetCounters); err == nil { - cm.resetCounters = setting + setting, err := strconv.ParseBool(cfg.ResetCounters) + if err != nil { + return nil, err } + cm.resetCounters = setting } cm.resetGauges = true if cfg.ResetGauges != "" { - if setting, err = strconv.ParseBool(cfg.ResetGauges); err == nil { - cm.resetGauges = setting + setting, err := strconv.ParseBool(cfg.ResetGauges) + if err != nil { + return nil, err } + cm.resetGauges = setting } cm.resetHistograms = true if cfg.ResetHistograms != "" { - if setting, err = strconv.ParseBool(cfg.ResetHistograms); err == nil { - cm.resetHistograms = setting + setting, err := strconv.ParseBool(cfg.ResetHistograms) + if err != nil { + return nil, err } + cm.resetHistograms = setting } cm.resetText = true if cfg.ResetText != "" { - if setting, err = strconv.ParseBool(cfg.ResetText); err == nil { - cm.resetText = setting + setting, err := strconv.ParseBool(cfg.ResetText) + if err != nil { + return nil, err } + cm.resetText = setting } - cfg.CheckManager.Debug = cm.Debug - cfg.CheckManager.Log = cm.Log + // check manager + { + cfg.CheckManager.Debug = cm.Debug + cfg.CheckManager.Log = cm.Log - check, err := checkmgr.NewCheckManager(&cfg.CheckManager) - if err != nil { - return nil, err + check, err := checkmgr.New(&cfg.CheckManager) + if err != nil { + return nil, err + } + cm.check = check } - cm.check = check - if _, err := cm.check.GetTrap(); err != nil { - return nil, err + // 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 initializes the CirconusMetrics instance based on -// configuration settings and sets the httptrap check url to -// which metrics should be sent. It then starts a perdiodic -// submission process of all metrics collected. +// Start deprecated NOP, automatic flush is started in New if flush interval > 0. func (m *CirconusMetrics) Start() { - go func() { - for _ = range time.NewTicker(m.flushInterval).C { - m.Flush() - } - }() + return +} + +// Ready returns true or false indicating if the check is ready to accept metrics +func (m *CirconusMetrics) Ready() bool { + return m.check.IsReady() } // Flush metrics kicks off the process of sending metrics to Circonus diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/submit.go b/vendor/github.com/circonus-labs/circonus-gometrics/submit.go index a8692c26c..3b0c0e0df 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/submit.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/submit.go @@ -22,12 +22,18 @@ import ( func (m *CirconusMetrics) submit(output map[string]interface{}, 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] marshling output %+v", err) + m.Log.Printf("[ERROR] marshaling output %+v", err) return } @@ -43,7 +49,7 @@ func (m *CirconusMetrics) submit(output map[string]interface{}, newMetrics map[s } func (m *CirconusMetrics) trapCall(payload []byte) (int, error) { - trap, err := m.check.GetTrap() + trap, err := m.check.GetSubmissionURL() if err != nil { return 0, err } diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/util.go b/vendor/github.com/circonus-labs/circonus-gometrics/util.go index b5e9f4777..4428e8985 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/util.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/util.go @@ -42,80 +42,103 @@ func (m *CirconusMetrics) Reset() { // 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() - m.gm.Lock() - defer m.gm.Unlock() + c := make(map[string]uint64, len(m.counters)+len(m.counterFuncs)) - 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() - - 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)) - //g = make(map[string]int64, len(m.gauges)+len(m.gaugeFuncs)) - 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)) - 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)) - 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.resetCounters { - m.counters = make(map[string]uint64) - m.counterFuncs = make(map[string]func() uint64) - } - - if m.resetGauges { - m.gauges = make(map[string]string) - m.gaugeFuncs = make(map[string]func() int64) - } - - if m.resetHistograms { - m.histograms = make(map[string]*Histogram) - } - - if m.resetText { - m.text = make(map[string]string) + if m.resetText && len(t) > 0 { m.textFuncs = make(map[string]func() string) } - return + return t } diff --git a/vendor/github.com/circonus-labs/circonusllhist/circonusllhist.go b/vendor/github.com/circonus-labs/circonusllhist/circonusllhist.go index cf4f482c1..2211bc5b8 100644 --- a/vendor/github.com/circonus-labs/circonusllhist/circonusllhist.go +++ b/vendor/github.com/circonus-labs/circonusllhist/circonusllhist.go @@ -11,11 +11,13 @@ import ( "errors" "fmt" "math" + "strconv" + "strings" "sync" ) const ( - DEFAULT_HIST_SIZE = int16(100) + DEFAULT_HIST_SIZE = uint16(100) ) var power_of_ten = [...]float64{ @@ -70,6 +72,14 @@ func NewBinFromFloat64(d float64) *Bin { hb.SetFromFloat64(d) return hb } + +type FastL2 struct { + l1, l2 int +} + +func (hb *Bin) fastl2() FastL2 { + return FastL2{l1: int(uint8(hb.exp)), l2: int(uint8(hb.val))} +} func (hb *Bin) SetFromFloat64(d float64) *Bin { hb.val = -1 if math.IsInf(d, 0) || math.IsNaN(d) { @@ -117,10 +127,7 @@ func (hb *Bin) SetFromFloat64(d float64) *Bin { return hb } func (hb *Bin) PowerOfTen() float64 { - idx := int(hb.exp) - if idx < 0 { - idx = 256 + idx - } + idx := int(uint8(hb.exp)) return power_of_ten[idx] } @@ -183,69 +190,79 @@ func (hb *Bin) Left() float64 { } func (h1 *Bin) Compare(h2 *Bin) int { - if h1.val == h2.val && h1.exp == h2.exp { - return 0 + var v1, v2 int + + // slide exp positive, + // shift by size of val + // multiple by (val != 0) + // then add or subtract val accordingly + + if h1.val >= 0 { + v1 = ((int(h1.exp)+256)<<8)*int(((h1.val|(^h1.val+1))>>8)&1) + int(h1.val) + } else { + v1 = ((int(h1.exp)+256)<<8)*int(((h1.val|(^h1.val+1))>>8)&1) - int(h1.val) } - if h1.val == -1 { - return 1 + if h2.val >= 0 { + v2 = ((int(h2.exp)+256)<<8)*int(((h2.val|(^h2.val+1))>>8)&1) + int(h2.val) + } else { + v2 = ((int(h2.exp)+256)<<8)*int(((h2.val|(^h2.val+1))>>8)&1) - int(h2.val) } - if h2.val == -1 { - return -1 - } - if h1.val == 0 { - if h2.val > 0 { - return 1 - } - return -1 - } - if h2.val == 0 { - if h1.val < 0 { - return 1 - } - return -1 - } - if h1.val < 0 && h2.val > 0 { - return 1 - } - if h1.val > 0 && h2.val < 0 { - return -1 - } - if h1.exp == h2.exp { - if h1.val < h2.val { - return 1 - } - return -1 - } - if h1.exp > h2.exp { - if h1.val < 0 { - return 1 - } - return -1 - } - if h1.exp < h2.exp { - if h1.val < 0 { - return -1 - } - return 1 - } - return 0 + + // return the difference + return v2 - v1 } // This histogram structure tracks values are two decimal digits of precision // with a bounded error that remains bounded upon composition type Histogram struct { - mutex sync.Mutex bvs []Bin - used int16 - allocd int16 + used uint16 + allocd uint16 + + lookup [256][]uint16 + + mutex sync.Mutex + useLocks bool } // New returns a new Histogram func New() *Histogram { return &Histogram{ - allocd: DEFAULT_HIST_SIZE, - used: 0, - bvs: make([]Bin, DEFAULT_HIST_SIZE), + allocd: DEFAULT_HIST_SIZE, + used: 0, + bvs: make([]Bin, DEFAULT_HIST_SIZE), + useLocks: true, + } +} + +// New returns a Histogram without locking +func NewNoLocks() *Histogram { + return &Histogram{ + allocd: DEFAULT_HIST_SIZE, + used: 0, + bvs: make([]Bin, DEFAULT_HIST_SIZE), + useLocks: false, + } +} + +// NewFromStrings returns a Histogram created from DecStrings strings +func NewFromStrings(strs []string, locks bool) (*Histogram, error) { + + bin, err := stringsToBin(strs) + if err != nil { + return nil, err + } + + return newFromBins(bin, locks), nil +} + +// NewFromBins returns a Histogram created from a bins struct slice +func newFromBins(bins []Bin, locks bool) *Histogram { + return &Histogram{ + allocd: uint16(len(bins) + 10), // pad it with 10 + used: uint16(len(bins)), + bvs: bins, + useLocks: locks, } } @@ -266,9 +283,24 @@ func (h *Histogram) Mean() float64 { // Reset forgets all bins in the histogram (they remain allocated) func (h *Histogram) Reset() { - h.mutex.Lock() + if h.useLocks { + h.mutex.Lock() + defer h.mutex.Unlock() + } + for i := 0; i < 256; i++ { + if h.lookup[i] != nil { + for j := range h.lookup[i] { + h.lookup[i][j] = 0 + } + } + } h.used = 0 - h.mutex.Unlock() +} + +// RecordIntScale records an integer scaler value, returning an error if the +// value is out of range. +func (h *Histogram) RecordIntScale(val, scale int) error { + return h.RecordIntScales(val, scale, 1) } // RecordValue records the given value, returning an error if the value is out @@ -304,14 +336,20 @@ func (h *Histogram) RecordCorrectedValue(v, expectedInterval int64) error { } // find where a new bin should go -func (h *Histogram) InternalFind(hb *Bin) (bool, int16) { +func (h *Histogram) InternalFind(hb *Bin) (bool, uint16) { if h.used == 0 { return false, 0 } + f2 := hb.fastl2() + if h.lookup[f2.l1] != nil { + if idx := h.lookup[f2.l1][f2.l2]; idx != 0 { + return true, idx - 1 + } + } rv := -1 - idx := int16(0) - l := int16(0) - r := h.used - 1 + idx := uint16(0) + l := int(0) + r := int(h.used - 1) for l < r { check := (r + l) / 2 rv = h.bvs[check].Compare(hb) @@ -327,7 +365,7 @@ func (h *Histogram) InternalFind(hb *Bin) (bool, int16) { if rv != 0 { rv = h.bvs[l].Compare(hb) } - idx = l + idx = uint16(l) if rv == 0 { return true, idx } @@ -339,10 +377,9 @@ func (h *Histogram) InternalFind(hb *Bin) (bool, int16) { } func (h *Histogram) InsertBin(hb *Bin, count int64) uint64 { - h.mutex.Lock() - defer h.mutex.Unlock() - if count == 0 { - return 0 + if h.useLocks { + h.mutex.Lock() + defer h.mutex.Unlock() } found, idx := h.InternalFind(hb) if !found { @@ -363,13 +400,20 @@ func (h *Histogram) InsertBin(hb *Bin, count int64) uint64 { h.bvs[idx].exp = hb.exp h.bvs[idx].count = uint64(count) h.used++ + for i := idx; i < h.used; i++ { + f2 := h.bvs[i].fastl2() + if h.lookup[f2.l1] == nil { + h.lookup[f2.l1] = make([]uint16, 256) + } + h.lookup[f2.l1][f2.l2] = uint16(i) + 1 + } return h.bvs[idx].count } var newval uint64 - if count < 0 { - newval = h.bvs[idx].count - uint64(-count) - } else { + if count >= 0 { newval = h.bvs[idx].count + uint64(count) + } else { + newval = h.bvs[idx].count - uint64(-count) } if newval < h.bvs[idx].count { //rolled newval = ^uint64(0) @@ -378,6 +422,39 @@ func (h *Histogram) InsertBin(hb *Bin, count int64) uint64 { return newval - h.bvs[idx].count } +// RecordIntScales records n occurrences of the given value, returning an error if +// the value is out of range. +func (h *Histogram) RecordIntScales(val, scale int, n int64) error { + sign := 1 + if val == 0 { + scale = 0 + } else { + if val < 0 { + val = 0 - val + sign = -1 + } + if val < 10 { + val *= 10 + scale -= 1 + } + for val > 100 { + val /= 10 + scale++ + } + } + if scale < -128 { + val = 0 + scale = 0 + } else if scale > 127 { + val = 0xff + scale = 0 + } + val *= sign + hb := Bin{val: int8(val), exp: int8(scale), count: 0} + h.InsertBin(&hb, n) + return nil +} + // RecordValues records n occurrences of the given value, returning an error if // the value is out of range. func (h *Histogram) RecordValues(v float64, n int64) error { @@ -389,11 +466,13 @@ func (h *Histogram) RecordValues(v float64, n int64) error { // Approximate mean func (h *Histogram) ApproxMean() float64 { - h.mutex.Lock() - defer h.mutex.Unlock() + if h.useLocks { + h.mutex.Lock() + defer h.mutex.Unlock() + } divisor := 0.0 sum := 0.0 - for i := int16(0); i < h.used; i++ { + for i := uint16(0); i < h.used; i++ { midpoint := h.bvs[i].Midpoint() cardinality := float64(h.bvs[i].count) divisor += cardinality @@ -407,10 +486,12 @@ func (h *Histogram) ApproxMean() float64 { // Approximate sum func (h *Histogram) ApproxSum() float64 { - h.mutex.Lock() - defer h.mutex.Unlock() + if h.useLocks { + h.mutex.Lock() + defer h.mutex.Unlock() + } sum := 0.0 - for i := int16(0); i < h.used; i++ { + for i := uint16(0); i < h.used; i++ { midpoint := h.bvs[i].Midpoint() cardinality := float64(h.bvs[i].count) sum += midpoint * cardinality @@ -419,10 +500,12 @@ func (h *Histogram) ApproxSum() float64 { } func (h *Histogram) ApproxQuantile(q_in []float64) ([]float64, error) { - h.mutex.Lock() - defer h.mutex.Unlock() + if h.useLocks { + h.mutex.Lock() + defer h.mutex.Unlock() + } q_out := make([]float64, len(q_in)) - i_q, i_b := 0, int16(0) + i_q, i_b := 0, uint16(0) total_cnt, bin_width, bin_left, lower_cnt, upper_cnt := 0.0, 0.0, 0.0, 0.0, 0.0 if len(q_in) == 0 { return q_out, nil @@ -485,8 +568,10 @@ func (h *Histogram) ApproxQuantile(q_in []float64) ([]float64, error) { // ValueAtQuantile returns the recorded value at the given quantile (0..1). func (h *Histogram) ValueAtQuantile(q float64) float64 { - h.mutex.Lock() - defer h.mutex.Unlock() + if h.useLocks { + h.mutex.Lock() + defer h.mutex.Unlock() + } q_in := make([]float64, 1) q_in[0] = q q_out, err := h.ApproxQuantile(q_in) @@ -505,16 +590,20 @@ func (h *Histogram) SignificantFigures() int64 { // Equals returns true if the two Histograms are equivalent, false if not. func (h *Histogram) Equals(other *Histogram) bool { - h.mutex.Lock() - other.mutex.Lock() - defer h.mutex.Unlock() - defer other.mutex.Unlock() + if h.useLocks { + h.mutex.Lock() + defer h.mutex.Unlock() + } + if other.useLocks { + other.mutex.Lock() + defer other.mutex.Unlock() + } switch { case h.used != other.used: return false default: - for i := int16(0); i < h.used; i++ { + for i := uint16(0); i < h.used; i++ { if h.bvs[i].Compare(&other.bvs[i]) != 0 { return false } @@ -527,8 +616,10 @@ func (h *Histogram) Equals(other *Histogram) bool { } func (h *Histogram) CopyAndReset() *Histogram { - h.mutex.Lock() - defer h.mutex.Unlock() + if h.useLocks { + h.mutex.Lock() + defer h.mutex.Unlock() + } newhist := &Histogram{ allocd: h.allocd, used: h.used, @@ -537,11 +628,20 @@ func (h *Histogram) CopyAndReset() *Histogram { h.allocd = DEFAULT_HIST_SIZE h.bvs = make([]Bin, DEFAULT_HIST_SIZE) h.used = 0 + for i := 0; i < 256; i++ { + if h.lookup[i] != nil { + for j := range h.lookup[i] { + h.lookup[i][j] = 0 + } + } + } return newhist } func (h *Histogram) DecStrings() []string { - h.mutex.Lock() - defer h.mutex.Unlock() + if h.useLocks { + h.mutex.Lock() + defer h.mutex.Unlock() + } out := make([]string, h.used) for i, bin := range h.bvs[0:h.used] { var buffer bytes.Buffer @@ -553,3 +653,37 @@ func (h *Histogram) DecStrings() []string { } return out } + +// takes the output of DecStrings and deserializes it into a Bin struct slice +func stringsToBin(strs []string) ([]Bin, error) { + + bins := make([]Bin, len(strs)) + for i, str := range strs { + + // H[0.0e+00]=1 + + // H[0.0e+00]= <1> + countString := strings.Split(str, "=")[1] + countInt, err := strconv.ParseInt(countString, 10, 64) + if err != nil { + return nil, err + } + + // H[ <0.0> e+00]=1 + valString := strings.Split(strings.Split(strings.Split(str, "=")[0], "e")[0], "[")[1] + valInt, err := strconv.ParseFloat(valString, 64) + if err != nil { + return nil, err + } + + // H[0.0e <+00> ]=1 + expString := strings.Split(strings.Split(strings.Split(str, "=")[0], "e")[1], "]")[0] + expInt, err := strconv.ParseInt(expString, 10, 8) + if err != nil { + return nil, err + } + bins[i] = *NewBinRaw(int8(valInt*10), int8(expInt), uint64(countInt)) + } + + return bins, nil +} diff --git a/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go b/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go index 84b22c944..f4596d80c 100644 --- a/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go +++ b/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go @@ -43,7 +43,7 @@ func DefaultClient() *http.Client { } // DefaultPooledClient returns a new http.Client with the same default values -// as http.Client, but with a non-shared Transport. Do not use this function +// as http.Client, but with a shared Transport. Do not use this function // for transient clients as it can leak file descriptors over time. Only use // this for clients that will be re-used for the same host(s). func DefaultPooledClient() *http.Client { diff --git a/vendor/vendor.json b/vendor/vendor.json index 008156732..570e53e79 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -200,28 +200,34 @@ "revisionTime": "2016-07-17T15:07:09Z" }, { - "checksumSHA1": "szvY4u7TlXkrQ3PW8wmyJaIFy0U=", + "checksumSHA1": "vhCArnFcQRM84iZcfMXka+2OzrE=", "path": "github.com/circonus-labs/circonus-gometrics", - "revision": "d17a8420c36e800fcb46bbd4d2a15b93c68605ea", - "revisionTime": "2016-11-09T19:23:37Z" + "revision": "a2c28f079ec3d4fdc17ed577cca75bee88a2da25", + "revisionTime": "2017-01-31T13:03:52Z" }, { - "checksumSHA1": "WUE6oF152uN5FcLmmq+nO3Yl7pU=", + "checksumSHA1": "sInms3AjZrjG/WCRcmS/NSzLUT4=", "path": "github.com/circonus-labs/circonus-gometrics/api", - "revision": "d17a8420c36e800fcb46bbd4d2a15b93c68605ea", - "revisionTime": "2016-11-09T19:23:37Z" + "revision": "a2c28f079ec3d4fdc17ed577cca75bee88a2da25", + "revisionTime": "2017-01-31T13:03:52Z" }, { - "checksumSHA1": "beRBHHy2+V6Ht4cACIMmZOCnzgg=", + "checksumSHA1": "bQhz/fcyZPmuHSH2qwC4ZtATy5c=", + "path": "github.com/circonus-labs/circonus-gometrics/api/config", + "revision": "a2c28f079ec3d4fdc17ed577cca75bee88a2da25", + "revisionTime": "2017-01-31T13:03:52Z" + }, + { + "checksumSHA1": "6hvd+YFb1eWFkc3pVcnOPPa2OVw=", "path": "github.com/circonus-labs/circonus-gometrics/checkmgr", - "revision": "d17a8420c36e800fcb46bbd4d2a15b93c68605ea", - "revisionTime": "2016-11-09T19:23:37Z" + "revision": "a2c28f079ec3d4fdc17ed577cca75bee88a2da25", + "revisionTime": "2017-01-31T13:03:52Z" }, { - "checksumSHA1": "C4Z7+l5GOpOCW5DcvNYzheGvQRE=", + "checksumSHA1": "VbfeVqeOM+dTNxCmpvmYS0LwQn0=", "path": "github.com/circonus-labs/circonusllhist", - "revision": "365d370cc1459bdcaceda3499453668373dea1d0", - "revisionTime": "2016-11-10T00:26:50Z" + "revision": "7d649b46cdc2cd2ed102d350688a75a4fd7778c6", + "revisionTime": "2016-11-21T13:51:53Z" }, { "path": "github.com/davecgh/go-spew/spew", @@ -288,14 +294,14 @@ { "checksumSHA1": "iP5slJJPRZUm0rfdII8OiATAACA=", "path": "github.com/docker/docker/pkg/idtools", - "revision": "02caa73df411debed164f520a6a1304778f8b88c", - "revisionTime": "2016-05-28T10:48:36Z" + "revision": "52debcd58ac91bf68503ce60561536911b74ff05", + "revisionTime": "2016-05-20T15:17:10Z" }, { "checksumSHA1": "iP5slJJPRZUm0rfdII8OiATAACA=", "path": "github.com/docker/docker/pkg/idtools", - "revision": "52debcd58ac91bf68503ce60561536911b74ff05", - "revisionTime": "2016-05-20T15:17:10Z" + "revision": "02caa73df411debed164f520a6a1304778f8b88c", + "revisionTime": "2016-05-28T10:48:36Z" }, { "checksumSHA1": "tdhmIGUaoOMEDymMC23qTS7bt0g=", @@ -330,14 +336,14 @@ { "checksumSHA1": "txf3EORYff4hO6PEvwBm2lyh1MU=", "path": "github.com/docker/docker/pkg/promise", - "revision": "da39e9a4f920a15683dd0f23923c302d4db6eed5", - "revisionTime": "2016-05-28T08:11:04Z" + "revision": "52debcd58ac91bf68503ce60561536911b74ff05", + "revisionTime": "2016-05-20T15:17:10Z" }, { "checksumSHA1": "txf3EORYff4hO6PEvwBm2lyh1MU=", "path": "github.com/docker/docker/pkg/promise", - "revision": "52debcd58ac91bf68503ce60561536911b74ff05", - "revisionTime": "2016-05-20T15:17:10Z" + "revision": "da39e9a4f920a15683dd0f23923c302d4db6eed5", + "revisionTime": "2016-05-28T08:11:04Z" }, { "checksumSHA1": "lThih54jzz9A4zHKEFb9SIV3Ed0=", @@ -648,8 +654,10 @@ "revision": "e4b2dc34c0f698ee04750bf2035d8b9384233e1b" }, { + "checksumSHA1": "Uzyon2091lmwacNsl1hCytjhHtg=", "path": "github.com/hashicorp/go-cleanhttp", - "revision": "875fb671b3ddc66f8e2f0acc33829c8cb989a38d" + "revision": "ad28ea4487f05916463e2423a55166280e8254b5", + "revisionTime": "2016-04-07T17:41:26Z" }, { "path": "github.com/hashicorp/go-getter",