Merge pull request #2260 from hashicorp/f-circonus-update

Update circonus vendor
This commit is contained in:
Michael Schurter 2017-02-01 11:20:46 -08:00 committed by GitHub
commit d470fe494d
39 changed files with 6382 additions and 803 deletions

View file

@ -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.

View file

@ -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:])

View file

@ -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.

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
)

View file

@ -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
}

View file

@ -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": "<h1>foo</h1>",
"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": "<p>{metric_name} ({value_type})<br /><strong>{metric_value}</strong><br /><span class=\"date\">{value_date}</span></p>",
"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": "<p>{metric_name} ({value_type})<br /><strong>{metric_value}</strong><br /><span class=\"date\">{value_date}</span></p>",
"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
}
]
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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")

View file

@ -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 its 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 &notes
}
// FetchCheckBySubmissionURL fetch a check configuration by submission_url
func (cm *CheckManager) fetchCheckBySubmissionURL(submissionURL api.URLType) (*api.Check, error) {
if string(submissionURL) == "" {
return nil, errors.New("[ERROR] Invalid submission URL (blank)")
}
u, err := url.Parse(string(submissionURL))
if err != nil {
return nil, err
}
// valid trap url: scheme://host[:port]/module/httptrap/UUID/secret
// does it smell like a valid trap url path
if !strings.Contains(u.Path, "/module/httptrap/") {
return nil, fmt.Errorf("[ERROR] Invalid submission URL '%s', unrecognized path", submissionURL)
}
// extract uuid
pathParts := strings.Split(strings.Replace(u.Path, "/module/httptrap/", "", 1), "/")
if len(pathParts) != 2 {
return nil, fmt.Errorf("[ERROR] Invalid submission URL '%s', UUID not where expected", submissionURL)
}
uuid := pathParts[0]
filter := api.SearchFilterType{"f__check_uuid": []string{uuid}}
checks, err := cm.apih.SearchChecks(nil, &filter)
if err != nil {
return nil, err
}
if len(*checks) == 0 {
return nil, fmt.Errorf("[ERROR] No checks found with UUID %s", uuid)
}
numActive := 0
checkID := -1
for idx, check := range *checks {
if check.Active {
numActive++
checkID = idx
}
}
if numActive > 1 {
return nil, fmt.Errorf("[ERROR] Multiple checks with same UUID %s", uuid)
}
check := (*checks)[checkID]
return &check, nil
}

View file

@ -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{}

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {

50
vendor/vendor.json vendored
View file

@ -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",