Update circonus-labs/circonus-gometrics.

This commit is contained in:
Sean Chittenden 2016-11-01 12:45:37 -07:00
parent c1f9d3ed61
commit f5a5587992
No known key found for this signature in database
GPG key ID: 4EBC9DC16C2E5E16
22 changed files with 717 additions and 263 deletions

View file

@ -1,69 +0,0 @@
# Setting up dev/test environment
Get go installed and environment configured
```sh
cd $GOPATH
mkdir -pv src/github.com/{hashicorp,armon,circonus-labs}
cd $GOPATH/src/github.com/hashicorp
git clone https://github.com/maier/consul.git
cd $GOPATH/src/github.com/armon
git clone https://github.com/maier/go-metrics.git
cd $GOPATH/src/github.com/circonus-labs
git clone https://github.com/maier/circonus-gometrics.git
cd $GOPATH/src/github.com/hashicorp/consul
make dev
```
In `$GOPATH/src/github.com/hashicorp/consul/bin` is the binary just created.
Create a consul configuration file somewhere (e.g. ~/testconfig.json) and add any applicable configuration settings. As an example:
```json
{
"datacenter": "mcfl",
"server": true,
"log_level": "debug",
"telemetry": {
"statsd_address": "127.0.0.1:8125",
"circonus_api_token": "...",
"circonus_api_host": "..."
}
}
```
StatsD was used as a check to see what metrics consul was sending and what metrics circonus was receiving. So, it can safely be elided.
Fill in appropriate cirocnus specific settings:
* circonus_api_token - required
* circonus_api_app - optional, default is circonus-gometrics
* circonus_api_host - optional, default is api.circonus.com (for dev stuff yon can use "http://..." to circumvent ssl)
* circonus_submission_url - optional
* circonus_submission_interval - optional, seconds, defaults to 10 seconds
* circonus_check_id - optional
* circonus_broker_id - optional (unless you want to use the public one, then add it)
The actual circonus-gometrics package has more configuraiton options, the above are exposed in the consul configuration.
CirconusMetrics.InstanceId is derived from consul's config.NodeName and config.Datacenter
CirconusMetrics.SearchTag is hardcoded as 'service:consul'
The defaults are taken for other options.
---
To run after creating the config:
`$GOPATH/src/github.com/hashicorp/consul/bin/consul agent -dev -config-file <config file>`
or, to add the ui (localhost:8500)
`$GOPATH/src/github.com/hashicorp/consul/bin/consul agent -dev -ui -config-file <config file>`

View file

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

View file

@ -24,17 +24,37 @@ import (
func main() {
log.Println("Configuring cgm")
logger := log.New(os.Stdout, "", log.LstdFlags)
logger.Println("Configuring cgm")
cmc := &cgm.Config{}
// Interval at which metrics are submitted to Circonus, default: 10 seconds
cmc.Interval = "10s" // 10 seconds
// cmc.Interval = "10s" // 10 seconds
// Enable debug messages, default: false
cmc.Debug = false
cmc.Debug = true
// Send debug messages to specific log.Logger instance
// default: if debug stderr, else, discard
//cmc.CheckManager.Log = ...
cmc.Log = logger
// 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
//
@ -53,10 +73,12 @@ func main() {
// otherwise: if an applicable check is NOT specified or found, an
// attempt will be made to automatically create one
//
// Pre-existing httptrap check submission_url
// Submission URL for an existing [httptrap] check
cmc.CheckManager.Check.SubmissionURL = os.Getenv("CIRCONUS_SUBMISION_URL")
// Pre-existing httptrap check id (check not check bundle)
cmc.CheckManager.Check.ID = ""
// 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,
@ -68,49 +90,63 @@ func main() {
// 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 - a specific tag which, when coupled with the instanceId serves to identify the
// origin and/or grouping of the metrics
// default: service:application name (e.g. service:consul)
cmc.CheckManager.Check.SearchTag = ""
// 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 = ""
// Check tags, array of strings, additional tags to add to a new check, default: none
//cmc.CheckManager.Check.Tags = []string{"category:tagname"}
// 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"
// cmc.CheckManager.Check.MaxURLAge = "5m"
// custom display name for check, default: "InstanceId /cgm"
cmc.CheckManager.Check.DisplayName = ""
// 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"
// 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 (e.g. can be used to dictate that a broker
// serving a specific location should be used. "dc:sfo", "location:new_york", "zone:us-west")
// if more than one broker has the tag, one will be selected randomly from the resulting list
// default: not used unless != ""
cmc.CheckManager.Broker.SelectTag = ""
// 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"
// if broker Id or SelectTag are not specified, a broker will be selected randomly
// 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.
log.Println("Creating new cgm instance")
logger.Println("Creating new cgm instance")
metrics, err := cgm.NewCirconusMetrics(cmc)
if err != nil {
@ -120,23 +156,44 @@ func main() {
src := rand.NewSource(time.Now().UnixNano())
rnd := rand.New(src)
log.Println("Starting cgm internal auto-flush timer")
logger.Println("Starting cgm internal auto-flush timer")
metrics.Start()
log.Println("Starting to send metrics")
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)
}()
// number of "sets" of metrics to send (a minute worth)
// 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")
// number of "sets" of metrics to send
max := 60
for i := 1; i < max; i++ {
log.Printf("\tmetric set %d of %d", i, 60)
metrics.Timing("ding", rnd.Float64()*10)
metrics.Increment("dong")
metrics.Gauge("dang", 10)
time.Sleep(1000 * time.Millisecond)
logger.Printf("\tmetric set %d of %d", i, 60)
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)
metrics.SetMetricTags("baz", []string{"cgm:reset_test", "cgm:test2"})
}
time.Sleep(time.Second)
}
log.Println("Flushing any outstanding metrics manually")
logger.Println("Flushing any outstanding metrics manually")
metrics.Flush()
}
@ -163,7 +220,7 @@ import (
func main() {
cmc := &cgm.Config{}
cmc.CheckManager.API.TokenKey = os.Getenv("CIRCONUS_API_TOKEN")
metrics, err := cgm.NewCirconusMetrics(cmc)
if err != nil {
panic(err)
@ -177,3 +234,5 @@ func main() {
}
```
Unless otherwise noted, the source files are distributed under the BSD-style license found in the LICENSE file.

View file

@ -1,3 +1,7 @@
// 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
package api
@ -20,9 +24,9 @@ const (
// a few sensible defaults
defaultAPIURL = "https://api.circonus.com/v2"
defaultAPIApp = "circonus-gometrics"
minRetryWait = 10 * time.Millisecond
maxRetryWait = 50 * time.Millisecond
maxRetries = 3
minRetryWait = 1 * time.Second
maxRetryWait = 15 * time.Second
maxRetries = 4 // equating to 1 + maxRetries total attempts
)
// TokenKeyType - Circonus API Token key
@ -43,8 +47,11 @@ type URLType string
// SearchQueryType search query
type SearchQueryType string
// SearchTagType search/select tag type
type SearchTagType string
// SearchFilterType search filter
type SearchFilterType string
// TagType search/select/custom tag(s) type
type TagType []string
// Config options for Circonus API
type Config struct {
@ -99,12 +106,13 @@ func NewAPI(ac *Config) (*API, error) {
a := &API{apiURL, key, app, ac.Debug, ac.Log}
a.Debug = ac.Debug
a.Log = ac.Log
if a.Debug && a.Log == nil {
a.Log = log.New(os.Stderr, "", log.LstdFlags)
}
if a.Log == nil {
if a.Debug {
a.Log = log.New(os.Stderr, "", log.LstdFlags)
} else {
a.Log = log.New(ioutil.Discard, "", log.LstdFlags)
}
a.Log = log.New(ioutil.Discard, "", log.LstdFlags)
}
return a, nil
@ -152,40 +160,56 @@ func (a *API) apiCall(reqMethod string, reqPath string, data []byte) ([]byte, er
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) {
if err != nil {
lastHTTPError = err
return true, err
}
// Check the response code. We retry on 500-range responses to allow
// the server time to recover, as 500's are typically not permanent
// errors and may relate to outages on the server side. This will catch
// invalid response codes as well, like 0 and 999.
// Retry on 429 (rate limit) as well.
if resp.StatusCode == 0 || resp.StatusCode >= 500 || resp.StatusCode == 429 {
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
lastHTTPError = fmt.Errorf("- last HTTP error: %d %+v", resp.StatusCode, readErr)
} else {
lastHTTPError = fmt.Errorf("- last HTTP error: %d %s", resp.StatusCode, string(body))
}
return true, nil
}
return false, nil
}
client := retryablehttp.NewClient()
client.RetryWaitMin = minRetryWait
client.RetryWaitMax = maxRetryWait
client.RetryMax = maxRetries
client.Logger = a.Log
// retryablehttp only groks log or no log
// but, outputs everything as [DEBUG] messages
if a.Debug {
client.Logger = a.Log
} else {
client.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
}
client.CheckRetry = retryPolicy
resp, err := client.Do(req)
if err != nil {
stdClient := &http.Client{}
dataReader.Seek(0, 0)
stdRequest, _ := http.NewRequest(reqMethod, reqURL, dataReader)
stdRequest.Header.Add("Accept", "application/json")
stdRequest.Header.Add("X-Circonus-Auth-Token", string(a.key))
stdRequest.Header.Add("X-Circonus-App-Name", string(a.app))
res, errSC := stdClient.Do(stdRequest)
if errSC != nil {
return nil, fmt.Errorf("[ERROR] fetching %s: %s", reqURL, errSC)
if lastHTTPError != nil {
return nil, lastHTTPError
}
if res != nil && res.Body != nil {
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
if a.Debug {
a.Log.Printf("[DEBUG] %v\n", string(body))
}
return nil, fmt.Errorf("[ERROR] %s", string(body))
}
return nil, fmt.Errorf("[ERROR] fetching %s: %s", reqURL, err)
return nil, fmt.Errorf("[ERROR] %s: %+v", reqURL, err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[ERROR] reading body %+v", err)
return nil, fmt.Errorf("[ERROR] reading response %+v", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {

View file

@ -1,8 +1,13 @@
// 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"
"strings"
)
// BrokerDetail instance attributes
@ -51,8 +56,8 @@ func (a *API) FetchBrokerByCID(cid CIDType) (*Broker, error) {
}
// FetchBrokerListByTag return list of brokers with a specific tag
func (a *API) FetchBrokerListByTag(searchTag SearchTagType) ([]Broker, error) {
query := SearchQueryType(fmt.Sprintf("f__tags_has=%s", searchTag))
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)
}
@ -66,7 +71,9 @@ func (a *API) BrokerSearch(query SearchQueryType) ([]Broker, error) {
}
var brokers []Broker
json.Unmarshal(result, &brokers)
if err := json.Unmarshal(result, &brokers); err != nil {
return nil, err
}
return brokers, nil
}
@ -79,7 +86,9 @@ func (a *API) FetchBrokerList() ([]Broker, error) {
}
var response []Broker
json.Unmarshal(result, &response)
if err := json.Unmarshal(result, &response); err != nil {
return nil, err
}
return response, nil
}

View file

@ -1,3 +1,7 @@
// 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 (
@ -36,7 +40,9 @@ func (a *API) FetchCheckByCID(cid CIDType) (*Check, error) {
}
check := new(Check)
json.Unmarshal(result, check)
if err := json.Unmarshal(result, check); err != nil {
return nil, err
}
return check, nil
}
@ -52,21 +58,20 @@ func (a *API) FetchCheckBySubmissionURL(submissionURL URLType) (*Check, error) {
// valid trap url: scheme://host[:port]/module/httptrap/UUID/secret
// does it smell like a valid trap url path
if u.Path[:17] != "/module/httptrap/" {
if !strings.Contains(u.Path, "/module/httptrap/") {
return nil, fmt.Errorf("[ERROR] Invalid submission URL '%s', unrecognized path", submissionURL)
}
// extract uuid/secret
pathParts := strings.Split(u.Path[17:len(u.Path)], "/")
// 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]
query := SearchQueryType(fmt.Sprintf("f__check_uuid=%s", uuid))
filter := SearchFilterType(fmt.Sprintf("f__check_uuid=%s", uuid))
checks, err := a.CheckSearch(query)
checks, err := a.CheckFilterSearch(filter)
if err != nil {
return nil, err
}
@ -93,9 +98,9 @@ func (a *API) FetchCheckBySubmissionURL(submissionURL URLType) (*Check, error) {
}
// CheckSearch returns a list of checks matching a query/filter
// CheckSearch returns a list of checks matching a search query
func (a *API) CheckSearch(query SearchQueryType) ([]Check, error) {
queryURL := fmt.Sprintf("/check?%s", string(query))
queryURL := fmt.Sprintf("/check?search=%s", string(query))
result, err := a.Get(queryURL)
if err != nil {
@ -103,7 +108,26 @@ func (a *API) CheckSearch(query SearchQueryType) ([]Check, error) {
}
var checks []Check
json.Unmarshal(result, &checks)
if err := json.Unmarshal(result, &checks); err != nil {
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
}

View file

@ -1,3 +1,7 @@
// 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 (
@ -10,14 +14,22 @@ 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"`
Name string `json:"name"`
Type string `json:"type"`
Units string `json:"units"`
Status string `json:"status"`
Tags []string `json:"tags"`
}
// CheckBundle definition
@ -28,7 +40,7 @@ type CheckBundle struct {
Created int `json:"_created,omitempty"`
LastModified int `json:"_last_modified,omitempty"`
LastModifedBy string `json:"_last_modifed_by,omitempty"`
ReverseConnectUrls []string `json:"_reverse_connection_urls,omitempty"`
ReverseConnectURLs []string `json:"_reverse_connection_urls"`
Brokers []string `json:"brokers"`
Config CheckBundleConfig `json:"config"`
DisplayName string `json:"display_name"`
@ -57,7 +69,9 @@ func (a *API) FetchCheckBundleByCID(cid CIDType) (*CheckBundle, error) {
}
checkBundle := &CheckBundle{}
json.Unmarshal(result, checkBundle)
if err := json.Unmarshal(result, checkBundle); err != nil {
return nil, err
}
return checkBundle, nil
}
@ -73,9 +87,8 @@ func (a *API) CheckBundleSearch(searchCriteria SearchQueryType) ([]CheckBundle,
}
var results []CheckBundle
err = json.Unmarshal(response, &results)
if err != nil {
return nil, fmt.Errorf("[ERROR] Parsing JSON response %+v", err)
if err := json.Unmarshal(response, &results); err != nil {
return nil, err
}
return results, nil
@ -94,8 +107,7 @@ func (a *API) CreateCheckBundle(config CheckBundle) (*CheckBundle, error) {
}
checkBundle := &CheckBundle{}
err = json.Unmarshal(response, checkBundle)
if err != nil {
if err := json.Unmarshal(response, checkBundle); err != nil {
return nil, err
}
@ -105,7 +117,7 @@ func (a *API) CreateCheckBundle(config CheckBundle) (*CheckBundle, error) {
// UpdateCheckBundle updates a check bundle configuration
func (a *API) UpdateCheckBundle(config *CheckBundle) (*CheckBundle, error) {
if a.Debug {
a.Log.Printf("[DEBUG] Updating check bundle with new metrics.")
a.Log.Printf("[DEBUG] Updating check bundle.")
}
cfgJSON, err := json.Marshal(config)
@ -119,8 +131,7 @@ func (a *API) UpdateCheckBundle(config *CheckBundle) (*CheckBundle, error) {
}
checkBundle := &CheckBundle{}
err = json.Unmarshal(response, checkBundle)
if err != nil {
if err := json.Unmarshal(response, checkBundle); err != nil {
return nil, err
}

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package checkmgr
import (
@ -75,7 +79,7 @@ func (cm *CheckManager) selectBroker() (*api.Broker, error) {
var brokerList []api.Broker
var err error
if cm.brokerSelectTag != "" {
if len(cm.brokerSelectTag) > 0 {
brokerList, err = cm.apih.FetchBrokerListByTag(cm.brokerSelectTag)
if err != nil {
return nil, err

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package checkmgr
import (
@ -70,8 +74,7 @@ func (cm *CheckManager) fetchCert() ([]byte, error) {
}
cadata := new(CACert)
err = json.Unmarshal(response, cadata)
if err != nil {
if err := json.Unmarshal(response, cadata); err != nil {
return nil, err
}

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package checkmgr
import (
@ -13,6 +17,50 @@ import (
"github.com/circonus-labs/circonus-gometrics/api"
)
// UpdateCheck determines if the check needs to be updated (new metrics, tags, etc.)
func (cm *CheckManager) UpdateCheck(newMetrics map[string]*api.CheckBundleMetric) {
// only if check manager is enabled
if !cm.enabled {
return
}
// only if checkBundle has been populated
if cm.checkBundle == nil {
return
}
cm.addNewMetrics(newMetrics)
if len(cm.metricTags) > 0 {
for metricName, metricTags := range cm.metricTags {
// note: if a tag has been added (queued) for a metric which never gets sent
// the tags will be discarded. (setting tags does not *create* metrics.)
cm.AddMetricTags(metricName, metricTags, false)
cm.mtmu.Lock()
delete(cm.metricTags, metricName)
cm.mtmu.Unlock()
}
}
if cm.forceCheckUpdate {
newCheckBundle, err := cm.apih.UpdateCheckBundle(cm.checkBundle)
if err != nil {
cm.Log.Printf("[ERROR] updating check bundle %v", err)
return
}
cm.forceCheckUpdate = false
cm.cbmu.Lock()
cm.checkBundle = newCheckBundle
cm.cbmu.Unlock()
cm.inventoryMetrics()
cm.cbmu.Unlock()
}
}
// Initialize CirconusMetrics instance. Attempt to find a check otherwise create one.
// use cases:
//
@ -28,6 +76,8 @@ func (cm *CheckManager) initializeTrapURL() error {
cm.trapmu.Lock()
defer cm.trapmu.Unlock()
// special case short-circuit: just send to a url, no check management
// up to user to ensure that if url is https that it will work (e.g. not self-signed)
if cm.checkSubmissionURL != "" {
if !cm.enabled {
cm.trapURL = cm.checkSubmissionURL
@ -50,6 +100,9 @@ func (cm *CheckManager) initializeTrapURL() error {
if err != nil {
return err
}
if !check.Active {
return fmt.Errorf("[ERROR] Check ID %v is not active", check.Cid)
}
// extract check id from check object returned from looking up using submission url
// set m.CheckId to the id
// set m.SubmissionUrl to "" to prevent trying to search on it going forward
@ -71,10 +124,13 @@ func (cm *CheckManager) initializeTrapURL() error {
if err != nil {
return err
}
if !check.Active {
return fmt.Errorf("[ERROR] Check ID %v is not active", check.Cid)
}
} else {
searchCriteria := fmt.Sprintf(
"(active:1)(host:\"%s\")(type:\"%s\")(tags:%s)",
cm.checkInstanceID, cm.checkType, cm.checkSearchTag)
cm.checkInstanceID, cm.checkType, strings.Join(cm.checkSearchTag, ","))
checkBundle, err = cm.checkBundleSearch(searchCriteria)
if err != nil {
return err
@ -112,8 +168,19 @@ func (cm *CheckManager) initializeTrapURL() error {
cm.checkBundle = checkBundle
cm.inventoryMetrics()
// url to which metrics should be PUT
cm.trapURL = api.URLType(checkBundle.Config.SubmissionURL)
// determine the trap url to which metrics should be PUT
if checkBundle.Type == "httptrap" {
cm.trapURL = api.URLType(checkBundle.Config.SubmissionURL)
} else {
// build a submission_url for non-httptrap checks out of mtev_reverse url
if len(checkBundle.ReverseConnectURLs) == 0 {
return fmt.Errorf("%s is not an HTTPTRAP check and no reverse connection urls found", checkBundle.Checks[0])
}
mtevURL := checkBundle.ReverseConnectURLs[0]
mtevURL = strings.Replace(mtevURL, "mtev_reverse", "https", 1)
mtevURL = strings.Replace(mtevURL, "check", "module/httptrap", 1)
cm.trapURL = api.URLType(fmt.Sprintf("%s/%s", mtevURL, checkBundle.Config.ReverseSecret))
}
// used when sending as "ServerName" get around certs not having IP SANS
// (cert created with server name as CN but IP used in trap url)
@ -181,7 +248,7 @@ func (cm *CheckManager) createNewCheck() (*api.CheckBundle, *api.Broker, error)
Notes: "",
Period: 60,
Status: statusActive,
Tags: append([]string{string(cm.checkSearchTag)}, cm.checkTags...),
Tags: append(cm.checkSearchTag, cm.checkTags...),
Target: string(cm.checkInstanceID),
Timeout: 10,
Type: string(cm.checkType),

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package checkmgr provides a check management interace to circonus-gometrics
package checkmgr
@ -12,6 +16,7 @@ import (
"os"
"path"
"strconv"
"strings"
"sync"
"time"
@ -54,7 +59,7 @@ type CheckConfig struct {
// used to search for a check to use
// used as check.target when creating a check
InstanceID string
// unique check searching tag
// 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
@ -64,7 +69,7 @@ type CheckConfig struct {
Secret string
// additional tags to add to a check (when creating a check)
// these tags will not be added to an existing check
Tags []string
Tags string
// max amount of time to to hold on to a submission url
// when a given submission fails (due to retries) if the
// time the url was last updated is > than this, the trap
@ -83,8 +88,8 @@ type CheckConfig struct {
type BrokerConfig struct {
// a specific broker id (numeric portion of cid)
ID string
// a tag that can be used to select 1-n brokers from which to select
// when creating a new check (e.g. datacenter:abc)
// one or more tags used to select 1-n brokers from which to select
// when creating a new check (e.g. datacenter:abc or loc:dfw,dc:abc)
SelectTag string
// for a broker to be considered viable it must respond to a
// connection attempt within this amount of time e.g. 200ms, 2s, 1m
@ -114,7 +119,7 @@ type CheckInstanceIDType string
type CheckSecretType string
// CheckTagsType check tags
type CheckTagsType []string
type CheckTagsType string
// CheckDisplayNameType check display name
type CheckDisplayNameType string
@ -133,20 +138,26 @@ type CheckManager struct {
checkType CheckTypeType
checkID api.IDType
checkInstanceID CheckInstanceIDType
checkSearchTag api.SearchTagType
checkSearchTag api.TagType
checkSecret CheckSecretType
checkTags CheckTagsType
checkTags api.TagType
checkSubmissionURL api.URLType
checkDisplayName CheckDisplayNameType
forceMetricActivation bool
forceCheckUpdate bool
// metric tags
metricTags map[string][]string
mtmu sync.Mutex
// broker
brokerID api.IDType
brokerSelectTag api.SearchTagType
brokerSelectTag api.TagType
brokerMaxResponseTime time.Duration
// state
checkBundle *api.CheckBundle
cbmu sync.Mutex
availableMetrics map[string]bool
trapURL api.URLType
trapCN BrokerCNType
@ -174,14 +185,12 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) {
}
cm.Debug = cfg.Debug
cm.Log = cfg.Log
if cm.Debug && cm.Log == nil {
cm.Log = log.New(os.Stderr, "", log.LstdFlags)
}
if cm.Log == nil {
if cm.Debug {
cm.Log = log.New(os.Stderr, "", log.LstdFlags)
} else {
cm.Log = log.New(ioutil.Discard, "", log.LstdFlags)
}
cm.Log = log.New(ioutil.Discard, "", log.LstdFlags)
}
if cfg.Check.SubmissionURL != "" {
@ -229,9 +238,7 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) {
cm.checkInstanceID = CheckInstanceIDType(cfg.Check.InstanceID)
cm.checkDisplayName = CheckDisplayNameType(cfg.Check.DisplayName)
cm.checkSearchTag = api.SearchTagType(cfg.Check.SearchTag)
cm.checkSecret = CheckSecretType(cfg.Check.Secret)
cm.checkTags = cfg.Check.Tags
fma := defaultForceMetricActivation
if cfg.Check.ForceMetricActivation != "" {
@ -252,8 +259,14 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) {
cm.checkInstanceID = CheckInstanceIDType(fmt.Sprintf("%s:%s", hn, an))
}
if cm.checkSearchTag == "" {
cm.checkSearchTag = api.SearchTagType(fmt.Sprintf("service:%s", an))
if cfg.Check.SearchTag == "" {
cm.checkSearchTag = []string{fmt.Sprintf("service:%s", an)}
} else {
cm.checkSearchTag = strings.Split(strings.Replace(cfg.Check.SearchTag, " ", "", -1), ",")
}
if cfg.Check.Tags != "" {
cm.checkTags = strings.Split(strings.Replace(cfg.Check.Tags, " ", "", -1), ",")
}
if cm.checkDisplayName == "" {
@ -282,7 +295,9 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) {
}
cm.brokerID = api.IDType(id)
cm.brokerSelectTag = api.SearchTagType(cfg.Broker.SelectTag)
if cfg.Broker.SelectTag != "" {
cm.brokerSelectTag = strings.Split(strings.Replace(cfg.Broker.SelectTag, " ", "", -1), ",")
}
dur = cfg.Broker.MaxResponseTime
if dur == "" {
@ -296,6 +311,7 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) {
// metrics
cm.availableMetrics = make(map[string]bool)
cm.metricTags = make(map[string][]string)
if err := cm.initializeTrapURL(); err != nil {
return nil, err

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package checkmgr
import (
@ -25,44 +29,106 @@ func (cm *CheckManager) ActivateMetric(name string) bool {
return false
}
// AddNewMetrics updates a check bundle with new metrics
func (cm *CheckManager) AddNewMetrics(newMetrics map[string]*api.CheckBundleMetric) {
// only if check manager is enabled
if !cm.enabled {
return
// AddMetricTags updates check bundle metrics with tags
func (cm *CheckManager) AddMetricTags(metricName string, tags []string, appendTags bool) bool {
tagsUpdated := false
if len(tags) == 0 {
return tagsUpdated
}
// only if checkBundle has been populated
if cm.checkBundle == nil {
return
metricFound := false
for metricIdx, metric := range cm.checkBundle.Metrics {
if metric.Name == metricName {
metricFound = true
numNewTags := countNewTags(metric.Tags, tags)
if numNewTags == 0 {
if appendTags {
break // no new tags to add
} else if len(metric.Tags) == len(tags) {
break // no new tags and old/new same length
}
}
if appendTags {
metric.Tags = append(metric.Tags, tags...)
} else {
metric.Tags = tags
}
cm.cbmu.Lock()
cm.checkBundle.Metrics[metricIdx] = metric
cm.cbmu.Unlock()
tagsUpdated = true
}
}
newCheckBundle := cm.checkBundle
numCurrMetrics := len(newCheckBundle.Metrics)
if tagsUpdated {
if cm.Debug {
action := "Set"
if appendTags {
action = "Added"
}
cm.Log.Printf("[DEBUG] %s metric tag(s) %s %v\n", action, metricName, tags)
}
cm.cbmu.Lock()
cm.forceCheckUpdate = true
cm.cbmu.Unlock()
} else {
if !metricFound {
if _, exists := cm.metricTags[metricName]; !exists {
if cm.Debug {
cm.Log.Printf("[DEBUG] Queing metric tag(s) %s %v\n", metricName, tags)
}
// queue the tags, the metric is new (e.g. not in the check yet)
cm.mtmu.Lock()
cm.metricTags[metricName] = append(cm.metricTags[metricName], tags...)
cm.mtmu.Unlock()
}
}
}
return tagsUpdated
}
// addNewMetrics updates a check bundle with new metrics
func (cm *CheckManager) addNewMetrics(newMetrics map[string]*api.CheckBundleMetric) bool {
updatedCheckBundle := false
if cm.checkBundle == nil || len(newMetrics) == 0 {
return updatedCheckBundle
}
cm.cbmu.Lock()
numCurrMetrics := len(cm.checkBundle.Metrics)
numNewMetrics := len(newMetrics)
if numCurrMetrics+numNewMetrics >= cap(newCheckBundle.Metrics) {
if numCurrMetrics+numNewMetrics >= cap(cm.checkBundle.Metrics) {
nm := make([]api.CheckBundleMetric, numCurrMetrics+numNewMetrics)
copy(nm, newCheckBundle.Metrics)
newCheckBundle.Metrics = nm
copy(nm, cm.checkBundle.Metrics)
cm.checkBundle.Metrics = nm
}
newCheckBundle.Metrics = newCheckBundle.Metrics[0 : numCurrMetrics+numNewMetrics]
cm.checkBundle.Metrics = cm.checkBundle.Metrics[0 : numCurrMetrics+numNewMetrics]
i := 0
for _, metric := range newMetrics {
newCheckBundle.Metrics[numCurrMetrics+i] = *metric
cm.checkBundle.Metrics[numCurrMetrics+i] = *metric
i++
updatedCheckBundle = true
}
checkBundle, err := cm.apih.UpdateCheckBundle(newCheckBundle)
if err != nil {
cm.Log.Printf("[ERROR] updating check bundle with new metrics %v", err)
return
if updatedCheckBundle {
cm.forceCheckUpdate = true
}
cm.checkBundle = checkBundle
cm.inventoryMetrics()
cm.cbmu.Unlock()
return updatedCheckBundle
}
// inventoryMetrics creates list of active metrics in check bundle
@ -73,3 +139,31 @@ func (cm *CheckManager) inventoryMetrics() {
}
cm.availableMetrics = availableMetrics
}
// countNewTags returns a count of new tags which do not exist in the current list of tags
func countNewTags(currTags []string, newTags []string) int {
if len(newTags) == 0 {
return 0
}
if len(currTags) == 0 {
return len(newTags)
}
newTagCount := 0
for _, newTag := range newTags {
found := false
for _, currTag := range currTags {
if newTag == currTag {
found = true
break
}
}
if !found {
newTagCount++
}
}
return newTagCount
}

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package circonusgometrics provides instrumentation for your applications in the form
// of counters, gauges and histograms and allows you to publish them to
// Circonus
@ -30,6 +34,7 @@ import (
"io/ioutil"
"log"
"os"
"strconv"
"sync"
"time"
@ -43,8 +48,12 @@ const (
// Config options for circonus-gometrics
type Config struct {
Log *log.Logger
Debug bool
Log *log.Logger
Debug bool
ResetCounters string // reset/delete counters on flush (default true)
ResetGauges string // reset/delete gauges on flush (default true)
ResetHistograms string // reset/delete histograms on flush (default true)
ResetText string // reset/delete text on flush (default true)
// API, Check and Broker configuration options
CheckManager checkmgr.Config
@ -55,12 +64,16 @@ type Config struct {
// CirconusMetrics state
type CirconusMetrics struct {
Log *log.Logger
Debug bool
flushInterval time.Duration
flushing bool
flushmu sync.Mutex
check *checkmgr.CheckManager
Log *log.Logger
Debug bool
resetCounters bool
resetGauges bool
resetHistograms bool
resetText bool
flushInterval time.Duration
flushing bool
flushmu sync.Mutex
check *checkmgr.CheckManager
counters map[string]uint64
cm sync.Mutex
@ -68,7 +81,7 @@ type CirconusMetrics struct {
counterFuncs map[string]func() uint64
cfm sync.Mutex
gauges map[string]int64
gauges map[string]string
gm sync.Mutex
gaugeFuncs map[string]func() int64
@ -94,7 +107,7 @@ func NewCirconusMetrics(cfg *Config) (*CirconusMetrics, error) {
cm := &CirconusMetrics{
counters: make(map[string]uint64),
counterFuncs: make(map[string]func() uint64),
gauges: make(map[string]int64),
gauges: make(map[string]string),
gaugeFuncs: make(map[string]func() int64),
histograms: make(map[string]*Histogram),
text: make(map[string]string),
@ -102,12 +115,10 @@ func NewCirconusMetrics(cfg *Config) (*CirconusMetrics, error) {
}
cm.Debug = cfg.Debug
if cm.Debug {
if cfg.Log == nil {
cm.Log = log.New(os.Stderr, "", log.LstdFlags)
} else {
cm.Log = cfg.Log
}
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)
@ -124,6 +135,36 @@ func NewCirconusMetrics(cfg *Config) (*CirconusMetrics, error) {
}
cm.flushInterval = dur
var setting bool
cm.resetCounters = true
if cfg.ResetCounters != "" {
if setting, err = strconv.ParseBool(cfg.ResetCounters); err == nil {
cm.resetCounters = setting
}
}
cm.resetGauges = true
if cfg.ResetGauges != "" {
if setting, err = strconv.ParseBool(cfg.ResetGauges); err == nil {
cm.resetGauges = setting
}
}
cm.resetHistograms = true
if cfg.ResetHistograms != "" {
if setting, err = strconv.ParseBool(cfg.ResetHistograms); err == nil {
cm.resetHistograms = setting
}
}
cm.resetText = true
if cfg.ResetText != "" {
if setting, err = strconv.ParseBool(cfg.ResetText); err == nil {
cm.resetText = setting
}
}
cfg.CheckManager.Debug = cm.Debug
cfg.CheckManager.Log = cm.Log
@ -242,7 +283,13 @@ func (m *CirconusMetrics) Flush() {
}
}
m.submit(output, newMetrics)
if len(output) > 0 {
m.submit(output, newMetrics)
} else {
if m.Debug {
m.Log.Println("[DEBUG] No metrics to send, skipping")
}
}
m.flushmu.Lock()
m.flushing = false

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package circonusgometrics
// A Counter is a monotonically increasing unsigned integer.

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package circonusgometrics
// A Gauge is an instantaneous measurement of a value.
@ -5,16 +9,20 @@ package circonusgometrics
// Use a gauge to track metrics which increase and decrease (e.g., amount of
// free memory).
import (
"fmt"
)
// Gauge sets a gauge to a value
func (m *CirconusMetrics) Gauge(metric string, val int64) {
func (m *CirconusMetrics) Gauge(metric string, val interface{}) {
m.SetGauge(metric, val)
}
// SetGauge sets a gauge to a value
func (m *CirconusMetrics) SetGauge(metric string, val int64) {
func (m *CirconusMetrics) SetGauge(metric string, val interface{}) {
m.gm.Lock()
defer m.gm.Unlock()
m.gauges[metric] = val
m.gauges[metric] = m.gaugeValString(val)
}
// RemoveGauge removes a gauge
@ -37,3 +45,37 @@ func (m *CirconusMetrics) RemoveGaugeFunc(metric string) {
defer m.gfm.Unlock()
delete(m.gaugeFuncs, metric)
}
// gaugeValString converts an interface value (of a supported type) to a string
func (m *CirconusMetrics) gaugeValString(val interface{}) string {
vs := ""
switch v := val.(type) {
default:
// ignore it, unsupported type
case int:
vs = fmt.Sprintf("%d", v)
case int8:
vs = fmt.Sprintf("%d", v)
case int16:
vs = fmt.Sprintf("%d", v)
case int32:
vs = fmt.Sprintf("%d", v)
case int64:
vs = fmt.Sprintf("%d", v)
case uint:
vs = fmt.Sprintf("%d", v)
case uint8:
vs = fmt.Sprintf("%d", v)
case uint16:
vs = fmt.Sprintf("%d", v)
case uint32:
vs = fmt.Sprintf("%d", v)
case uint64:
vs = fmt.Sprintf("%d", v)
case float32:
vs = fmt.Sprintf("%f", v)
case float64:
vs = fmt.Sprintf("%f", v)
}
return vs
}

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package circonusgometrics
import (
@ -25,19 +29,20 @@ func (m *CirconusMetrics) RecordValue(metric string, val float64) {
// SetHistogramValue adds a value to a histogram
func (m *CirconusMetrics) SetHistogramValue(metric string, val float64) {
m.NewHistogram(metric)
hist := m.NewHistogram(metric)
m.histograms[metric].rw.Lock()
defer m.histograms[metric].rw.Unlock()
m.histograms[metric].hist.RecordValue(val)
m.hm.Lock()
hist.rw.Lock()
hist.hist.RecordValue(val)
hist.rw.Unlock()
m.hm.Unlock()
}
// RemoveHistogram removes a histogram
func (m *CirconusMetrics) RemoveHistogram(metric string) {
m.hm.Lock()
defer m.hm.Unlock()
delete(m.histograms, metric)
m.hm.Unlock()
}
// NewHistogram returns a histogram instance.
@ -67,7 +72,6 @@ func (h *Histogram) Name() string {
// RecordValue records the given value to a histogram instance
func (h *Histogram) RecordValue(v float64) {
h.rw.Lock()
defer h.rw.Unlock()
h.hist.RecordValue(v)
h.rw.Unlock()
}

View file

@ -0,0 +1,15 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package circonusgometrics
// SetMetricTags sets the tags for the named metric and flags a check update is needed
func (m *CirconusMetrics) SetMetricTags(name string, tags []string) bool {
return m.check.AddMetricTags(name, tags, false)
}
// AddMetricTags appends tags to any existing tags for the named metric and flags a check update is needed
func (m *CirconusMetrics) AddMetricTags(name string, tags []string) bool {
return m.check.AddMetricTags(name, tags, true)
}

View file

@ -1,9 +1,14 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package circonusgometrics
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
@ -16,9 +21,9 @@ import (
)
func (m *CirconusMetrics) submit(output map[string]interface{}, newMetrics map[string]*api.CheckBundleMetric) {
if len(newMetrics) > 0 {
m.check.AddNewMetrics(newMetrics)
}
// 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 {
@ -49,8 +54,32 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
if err != nil {
return 0, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
// keep last HTTP error in the event of retry failure
var lastHTTPError error
retryPolicy := func(resp *http.Response, err error) (bool, error) {
if err != nil {
lastHTTPError = err
return true, err
}
// Check the response code. We retry on 500-range responses to allow
// the server time to recover, as 500's are typically not permanent
// errors and may relate to outages on the server side. This will catch
// invalid response codes as well, like 0 and 999.
if resp.StatusCode == 0 || resp.StatusCode >= 500 {
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
lastHTTPError = fmt.Errorf("- last HTTP error: %d %+v", resp.StatusCode, readErr)
} else {
lastHTTPError = fmt.Errorf("- last HTTP error: %d %s", resp.StatusCode, string(body))
}
return true, nil
}
return false, nil
}
client := retryablehttp.NewClient()
if trap.URL.Scheme == "https" {
client.HTTPClient.Transport = &http.Transport{
@ -78,10 +107,17 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
DisableCompression: true,
}
}
client.RetryWaitMin = 10 * time.Millisecond
client.RetryWaitMax = 50 * time.Millisecond
client.RetryWaitMin = 1 * time.Second
client.RetryWaitMax = 5 * time.Second
client.RetryMax = 3
client.Logger = m.Log
// retryablehttp only groks log or no log
// but, outputs everything as [DEBUG] messages
if m.Debug {
client.Logger = m.Log
} else {
client.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
}
client.CheckRetry = retryPolicy
attempts := -1
client.RequestLogHook = func(logger *log.Logger, req *http.Request, retryNumber int) {
@ -90,6 +126,9 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
resp, err := client.Do(req)
if err != nil {
if lastHTTPError != nil {
return 0, fmt.Errorf("[ERROR] submitting: %+v %+v", err, lastHTTPError)
}
if attempts == client.RetryMax {
m.check.RefreshTrap()
}
@ -103,9 +142,8 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
}
var response map[string]interface{}
err = json.Unmarshal(body, &response)
if err != nil {
m.Log.Printf("[ERROR] parsing body, proceeding. %s\n", err)
if err := json.Unmarshal(body, &response); err != nil {
m.Log.Printf("[ERROR] parsing body, proceeding. %v (%s)\n", err, body)
}
if resp.StatusCode != 200 {

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package circonusgometrics
// A Text metric is an arbitrary string

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package circonusgometrics
import (

View file

@ -1,3 +1,7 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package circonusgometrics
import (
@ -29,7 +33,7 @@ func (m *CirconusMetrics) Reset() {
m.counters = make(map[string]uint64)
m.counterFuncs = make(map[string]func() uint64)
m.gauges = make(map[string]int64)
m.gauges = make(map[string]string)
m.gaugeFuncs = make(map[string]func() int64)
m.histograms = make(map[string]*Histogram)
m.text = make(map[string]string)
@ -37,7 +41,7 @@ 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]int64, h map[string]*circonusllhist.Histogram, t map[string]string) {
func (m *CirconusMetrics) snapshot() (c map[string]uint64, g map[string]string, h map[string]*circonusllhist.Histogram, t map[string]string) {
m.cm.Lock()
defer m.cm.Unlock()
@ -68,18 +72,21 @@ func (m *CirconusMetrics) snapshot() (c map[string]uint64, g map[string]int64, h
c[n] = f()
}
g = make(map[string]int64, 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
}
for n, f := range m.gaugeFuncs {
g[n] = f()
g[n] = m.gaugeValString(f())
}
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()
}
t = make(map[string]string, len(m.text)+len(m.textFuncs))
@ -91,5 +98,24 @@ func (m *CirconusMetrics) snapshot() (c map[string]uint64, g map[string]int64, h
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)
m.textFuncs = make(map[string]func() string)
}
return
}

18
vendor/vendor.json vendored
View file

@ -200,22 +200,22 @@
"revisionTime": "2016-07-17T15:07:09Z"
},
{
"checksumSHA1": "qNHg4l2+bg50XQUR094857jPOoQ=",
"checksumSHA1": "szvY4u7TlXkrQ3PW8wmyJaIFy0U=",
"path": "github.com/circonus-labs/circonus-gometrics",
"revision": "4d03e5666abdf78da571fcb930201f06b0c46662",
"revisionTime": "2016-07-21T19:08:58Z"
"revision": "f8e2e2ff37c349aed537b70c9430ded0ea473248",
"revisionTime": "2016-11-01T19:28:51Z"
},
{
"checksumSHA1": "IFiYTxu8jshL4A8BCttUaDhp1m4=",
"checksumSHA1": "dCnZd/wjQjTnUBTBEDpe0Y2pvTA=",
"path": "github.com/circonus-labs/circonus-gometrics/api",
"revision": "4d03e5666abdf78da571fcb930201f06b0c46662",
"revisionTime": "2016-07-21T19:08:58Z"
"revision": "f8e2e2ff37c349aed537b70c9430ded0ea473248",
"revisionTime": "2016-11-01T19:28:51Z"
},
{
"checksumSHA1": "+9vcRzlTdvEjH/Uf8fKC5MXdjNw=",
"checksumSHA1": "B0hN7c1dJ8n3MoZLktuWJoaYxRs=",
"path": "github.com/circonus-labs/circonus-gometrics/checkmgr",
"revision": "4d03e5666abdf78da571fcb930201f06b0c46662",
"revisionTime": "2016-07-21T19:08:58Z"
"revision": "f8e2e2ff37c349aed537b70c9430ded0ea473248",
"revisionTime": "2016-11-01T19:28:51Z"
},
{
"checksumSHA1": "C4Z7+l5GOpOCW5DcvNYzheGvQRE=",