Revert "Updates Circonus metrics library and adds support for display name and tags."

This reverts commit bd490ec937 from #2491.
pull/2498/head
James Phillips 2016-11-09 16:21:02 -08:00
parent 92ce2c9e39
commit 16f8e04bfe
No known key found for this signature in database
GPG Key ID: 77183E682AC5FC11
27 changed files with 316 additions and 971 deletions

View File

@ -805,8 +805,6 @@ func (c *Command) Run(args []string) int {
cfg.CheckManager.Check.ForceMetricActivation = config.Telemetry.CirconusCheckForceMetricActivation cfg.CheckManager.Check.ForceMetricActivation = config.Telemetry.CirconusCheckForceMetricActivation
cfg.CheckManager.Check.InstanceID = config.Telemetry.CirconusCheckInstanceID cfg.CheckManager.Check.InstanceID = config.Telemetry.CirconusCheckInstanceID
cfg.CheckManager.Check.SearchTag = config.Telemetry.CirconusCheckSearchTag cfg.CheckManager.Check.SearchTag = config.Telemetry.CirconusCheckSearchTag
cfg.CheckManager.Check.DisplayName = config.Telemetry.CirconusCheckDisplayName
cfg.CheckManager.Check.Tags = config.Telemetry.CirconusCheckTags
cfg.CheckManager.Broker.ID = config.Telemetry.CirconusBrokerID cfg.CheckManager.Broker.ID = config.Telemetry.CirconusBrokerID
cfg.CheckManager.Broker.SelectTag = config.Telemetry.CirconusBrokerSelectTag cfg.CheckManager.Broker.SelectTag = config.Telemetry.CirconusBrokerSelectTag
@ -814,6 +812,10 @@ func (c *Command) Run(args []string) int {
cfg.CheckManager.API.TokenApp = "consul" cfg.CheckManager.API.TokenApp = "consul"
} }
if cfg.CheckManager.Check.InstanceID == "" {
cfg.CheckManager.Check.InstanceID = fmt.Sprintf("%s:%s", config.NodeName, config.Datacenter)
}
if cfg.CheckManager.Check.SearchTag == "" { if cfg.CheckManager.Check.SearchTag == "" {
cfg.CheckManager.Check.SearchTag = "service:consul" cfg.CheckManager.Check.SearchTag = "service:consul"
} }

View File

@ -213,13 +213,6 @@ type Telemetry struct {
// narrow down the search results when neither a Submission URL or Check ID is provided. // narrow down the search results when neither a Submission URL or Check ID is provided.
// Default: service:app (e.g. service:consul) // Default: service:app (e.g. service:consul)
CirconusCheckSearchTag string `mapstructure:"circonus_check_search_tag"` CirconusCheckSearchTag string `mapstructure:"circonus_check_search_tag"`
// CirconusCheckTags is a comma separated list of tags to apply to the check. Note that
// the value of CirconusCheckSearchTag will always be added to the check.
// Default: none
CirconusCheckTags string `mapstructure:"circonus_check_tags"`
// CirconusCheckDisplayName is the name for the check which will be displayed in the Circonus UI.
// Default: value of CirconusCheckInstanceID
CirconusCheckDisplayName string `mapstructure:"circonus_check_display_name"`
// CirconusBrokerID is an explicit broker to use when creating a new check. The numeric portion // CirconusBrokerID is an explicit broker to use when creating a new check. The numeric portion
// of broker._cid. If metric management is enabled and neither a Submission URL nor Check ID // of broker._cid. If metric management is enabled and neither a Submission URL nor Check ID
// is provided, an attempt will be made to search for an existing check using Instance ID and // is provided, an attempt will be made to search for an existing check using Instance ID and
@ -1277,12 +1270,6 @@ func MergeConfig(a, b *Config) *Config {
if b.Telemetry.CirconusCheckSearchTag != "" { if b.Telemetry.CirconusCheckSearchTag != "" {
result.Telemetry.CirconusCheckSearchTag = b.Telemetry.CirconusCheckSearchTag result.Telemetry.CirconusCheckSearchTag = b.Telemetry.CirconusCheckSearchTag
} }
if b.Telemetry.CirconusCheckDisplayName != "" {
result.Telemetry.CirconusCheckDisplayName = b.Telemetry.CirconusCheckDisplayName
}
if b.Telemetry.CirconusCheckTags != "" {
result.Telemetry.CirconusCheckTags = b.Telemetry.CirconusCheckTags
}
if b.Telemetry.CirconusBrokerID != "" { if b.Telemetry.CirconusBrokerID != "" {
result.Telemetry.CirconusBrokerID = b.Telemetry.CirconusBrokerID result.Telemetry.CirconusBrokerID = b.Telemetry.CirconusBrokerID
} }

View File

@ -758,7 +758,6 @@ func TestDecodeConfig(t *testing.T) {
"circonus_submission_url": "https://submit.host.bar:123/one/two/three", "circonus_submission_url": "https://submit.host.bar:123/one/two/three",
"circonus_check_id": "12345", "circonus_check_force_metric_activation": "true", "circonus_check_id": "12345", "circonus_check_force_metric_activation": "true",
"circonus_check_instance_id": "a:b", "circonus_check_search_tag": "c:d", "circonus_check_instance_id": "a:b", "circonus_check_search_tag": "c:d",
"circonus_check_display_name": "node1:consul", "circonus_check_tags": "cat1:tag1,cat2:tag2",
"circonus_broker_id": "6789", "circonus_broker_select_tag": "e:f"} }` "circonus_broker_id": "6789", "circonus_broker_select_tag": "e:f"} }`
config, err = DecodeConfig(bytes.NewReader([]byte(input))) config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil { if err != nil {
@ -791,12 +790,6 @@ func TestDecodeConfig(t *testing.T) {
if config.Telemetry.CirconusCheckSearchTag != "c:d" { if config.Telemetry.CirconusCheckSearchTag != "c:d" {
t.Fatalf("bad: %#v", config) t.Fatalf("bad: %#v", config)
} }
if config.Telemetry.CirconusCheckDisplayName != "node1:consul" {
t.Fatalf("bad: %#v", config)
}
if config.Telemetry.CirconusCheckTags != "cat1:tag1,cat2:tag2" {
t.Fatalf("bad: %#v", config)
}
if config.Telemetry.CirconusBrokerID != "6789" { if config.Telemetry.CirconusBrokerID != "6789" {
t.Fatalf("bad: %#v", config) t.Fatalf("bad: %#v", config)
} }

View File

@ -1,28 +0,0 @@
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,37 +24,17 @@ import (
func main() { func main() {
logger := log.New(os.Stdout, "", log.LstdFlags) log.Println("Configuring cgm")
logger.Println("Configuring cgm")
cmc := &cgm.Config{} cmc := &cgm.Config{}
// Interval at which metrics are submitted to Circonus, default: 10 seconds // 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 // Enable debug messages, default: false
cmc.Debug = true cmc.Debug = false
// Send debug messages to specific log.Logger instance // Send debug messages to specific log.Logger instance
// default: if debug stderr, else, discard // default: if debug stderr, else, discard
cmc.Log = logger //cmc.CheckManager.Log = ...
// 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 // Circonus API configuration options
// //
@ -73,12 +53,10 @@ func main() {
// otherwise: if an applicable check is NOT specified or found, an // otherwise: if an applicable check is NOT specified or found, an
// attempt will be made to automatically create one // attempt will be made to automatically create one
// //
// Submission URL for an existing [httptrap] check // Pre-existing httptrap check submission_url
cmc.CheckManager.Check.SubmissionURL = os.Getenv("CIRCONUS_SUBMISION_URL") cmc.CheckManager.Check.SubmissionURL = os.Getenv("CIRCONUS_SUBMISION_URL")
// Pre-existing httptrap check id (check not check bundle)
// ID of an existing [httptrap] check (note: check id not check bundle id) cmc.CheckManager.Check.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 // 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: // httptrap check by using the circonus api to search for a check matching the following criteria:
// an active check, // an active check,
@ -90,63 +68,49 @@ func main() {
// default: 'hostname':'program name' // default: 'hostname':'program name'
// note: for a persistent instance that is ephemeral or transient where metric continuity is // 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. // desired set this explicitly so that the current hostname will not be used.
// cmc.CheckManager.Check.InstanceID = "" cmc.CheckManager.Check.InstanceID = ""
// Search tag - a specific tag which, when coupled with the instanceId serves to identify the
// Search tag - specific tag(s) used in conjunction with isntanceId to search for an // origin and/or grouping of the metrics
// existing check. comma separated string of tags (spaces will be removed, no commas // default: service:application name (e.g. service:consul)
// in tag elements). cmc.CheckManager.Check.SearchTag = ""
// 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 // Check secret, default: generated when a check needs to be created
// cmc.CheckManager.Check.Secret = "" cmc.CheckManager.Check.Secret = ""
// Check tags, array of strings, additional tags to add to a new check, default: none
// Additional tag(s) to add when *creating* a check. comma separated string //cmc.CheckManager.Check.Tags = []string{"category:tagname"}
// 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 // max amount of time to to hold on to a submission url
// when a given submission fails (due to retries) if the // when a given submission fails (due to retries) if the
// time the url was last updated is > than this, the trap // time the url was last updated is > than this, the trap
// url will be refreshed (e.g. if the broker is changed // url will be refreshed (e.g. if the broker is changed
// in the UI) default 5 minutes // in the UI) default 5 minutes
// cmc.CheckManager.Check.MaxURLAge = "5m" cmc.CheckManager.Check.MaxURLAge = "5m"
// custom display name for check, default: "InstanceId /cgm" // 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 // force metric activation - if a metric has been disabled via the UI
// the default behavior is to *not* re-activate the metric; this setting // the default behavior is to *not* re-activate the metric; this setting
// overrides the behavior and will re-activate the metric when it is // overrides the behavior and will re-activate the metric when it is
// encountered. "(true|false)", default "false" // encountered. "(true|false)", default "false"
// cmc.CheckManager.Check.ForceMetricActivation = "false" cmc.CheckManager.Check.ForceMetricActivation = "false"
// Broker configuration options // Broker configuration options
// //
// Broker ID of specific broker to use, default: random enterprise broker or // Broker ID of specific broker to use, default: random enterprise broker or
// Circonus default if no enterprise brokers are available. // Circonus default if no enterprise brokers are available.
// default: only used if set // default: only used if set
// cmc.CheckManager.Broker.ID = "" cmc.CheckManager.Broker.ID = ""
// used to select a broker with the same tag (e.g. can be used to dictate that a broker
// 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", "location:new_york", "zone:us-west")
// serving a specific location should be used. "dc:sfo", "loc:nyc,dc:nyc01", "zone:us-west") // if more than one broker has the tag, one will be selected randomly from the resulting list
// if more than one broker has the tag(s), one will be selected randomly from the resulting // default: not used unless != ""
// list. comma separated string of tags (spaces will be removed, no commas in tag elements). cmc.CheckManager.Broker.SelectTag = ""
// default: none
// cmc.CheckManager.Broker.SelectTag = ""
// longest time to wait for a broker connection (if latency is > the broker will // 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 // be considered invalid and not available for selection.), default: 500 milliseconds
// cmc.CheckManager.Broker.MaxResponseTime = "500ms" cmc.CheckManager.Broker.MaxResponseTime = "500ms"
// if broker Id or SelectTag are not specified, a broker will be selected randomly
// 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 // 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 // viable brokers are "active", have the "httptrap" module enabled, are reachable and respond
// within MaxResponseTime. // within MaxResponseTime.
logger.Println("Creating new cgm instance") log.Println("Creating new cgm instance")
metrics, err := cgm.NewCirconusMetrics(cmc) metrics, err := cgm.NewCirconusMetrics(cmc)
if err != nil { if err != nil {
@ -156,44 +120,23 @@ func main() {
src := rand.NewSource(time.Now().UnixNano()) src := rand.NewSource(time.Now().UnixNano())
rnd := rand.New(src) rnd := rand.New(src)
logger.Println("Starting cgm internal auto-flush timer") log.Println("Starting cgm internal auto-flush timer")
metrics.Start() metrics.Start()
logger.Println("Adding ctrl-c trap") log.Println("Starting to send metrics")
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) // number of "sets" of metrics to send (a minute worth)
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 max := 60
for i := 1; i < max; i++ { for i := 1; i < max; i++ {
logger.Printf("\tmetric set %d of %d", i, 60) log.Printf("\tmetric set %d of %d", i, 60)
metrics.Timing("ding", rnd.Float64()*10)
metrics.Timing("foo", rnd.Float64()*10) metrics.Increment("dong")
metrics.Increment("bar") metrics.Gauge("dang", 10)
metrics.Gauge("baz", 10) time.Sleep(1000 * time.Millisecond)
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)
} }
logger.Println("Flushing any outstanding metrics manually") log.Println("Flushing any outstanding metrics manually")
metrics.Flush() metrics.Flush()
} }
@ -220,7 +163,7 @@ import (
func main() { func main() {
cmc := &cgm.Config{} cmc := &cgm.Config{}
cmc.CheckManager.API.TokenKey = os.Getenv("CIRCONUS_API_TOKEN") cmc.CheckManager.API.TokenKey = os.Getenv("CIRCONUS_API_TOKEN")
metrics, err := cgm.NewCirconusMetrics(cmc) metrics, err := cgm.NewCirconusMetrics(cmc)
if err != nil { if err != nil {
panic(err) panic(err)
@ -234,5 +177,3 @@ func main() {
} }
``` ```
Unless otherwise noted, the source files are distributed under the BSD-style license found in the LICENSE file.

View File

@ -1,7 +1,3 @@
// 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 provides methods for interacting with the Circonus API
package api package api
@ -24,9 +20,9 @@ const (
// a few sensible defaults // a few sensible defaults
defaultAPIURL = "https://api.circonus.com/v2" defaultAPIURL = "https://api.circonus.com/v2"
defaultAPIApp = "circonus-gometrics" defaultAPIApp = "circonus-gometrics"
minRetryWait = 1 * time.Second minRetryWait = 10 * time.Millisecond
maxRetryWait = 15 * time.Second maxRetryWait = 50 * time.Millisecond
maxRetries = 4 // equating to 1 + maxRetries total attempts maxRetries = 3
) )
// TokenKeyType - Circonus API Token key // TokenKeyType - Circonus API Token key
@ -47,11 +43,8 @@ type URLType string
// SearchQueryType search query // SearchQueryType search query
type SearchQueryType string type SearchQueryType string
// SearchFilterType search filter // SearchTagType search/select tag type
type SearchFilterType string type SearchTagType string
// TagType search/select/custom tag(s) type
type TagType []string
// Config options for Circonus API // Config options for Circonus API
type Config struct { type Config struct {
@ -106,13 +99,12 @@ func NewAPI(ac *Config) (*API, error) {
a := &API{apiURL, key, app, ac.Debug, ac.Log} 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.Log == nil {
a.Log = log.New(ioutil.Discard, "", log.LstdFlags) if a.Debug {
a.Log = log.New(os.Stderr, "", log.LstdFlags)
} else {
a.Log = log.New(ioutil.Discard, "", log.LstdFlags)
}
} }
return a, nil return a, nil
@ -160,56 +152,40 @@ 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-Auth-Token", string(a.key))
req.Header.Add("X-Circonus-App-Name", string(a.app)) 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 := retryablehttp.NewClient()
client.RetryWaitMin = minRetryWait client.RetryWaitMin = minRetryWait
client.RetryWaitMax = maxRetryWait client.RetryWaitMax = maxRetryWait
client.RetryMax = maxRetries client.RetryMax = maxRetries
// retryablehttp only groks log or no log client.Logger = a.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) resp, err := client.Do(req)
if err != nil { if err != nil {
if lastHTTPError != nil { stdClient := &http.Client{}
return nil, lastHTTPError 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)
} }
return nil, fmt.Errorf("[ERROR] %s: %+v", reqURL, err)
}
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)
}
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("[ERROR] reading response %+v", err) return nil, fmt.Errorf("[ERROR] reading body %+v", err)
} }
if resp.StatusCode < 200 || resp.StatusCode >= 300 { if resp.StatusCode < 200 || resp.StatusCode >= 300 {

View File

@ -1,27 +1,20 @@
// 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 package api
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
) )
// BrokerDetail instance attributes // BrokerDetail instance attributes
type BrokerDetail struct { type BrokerDetail struct {
CN string `json:"cn"` CN string `json:"cn"`
ExternalHost string `json:"external_host"` IP string `json:"ipaddress"`
ExternalPort int `json:"external_port"` MinVer int `json:"minimum_version_required"`
IP string `json:"ipaddress"` Modules []string `json:"modules"`
MinVer int `json:"minimum_version_required"` Port int `json:"port"`
Modules []string `json:"modules"` Skew string `json:"skew"`
Port int `json:"port"` Status string `json:"status"`
Skew string `json:"skew"` Version int `json:"version"`
Status string `json:"status"`
Version int `json:"version"`
} }
// Broker definition // Broker definition
@ -58,8 +51,8 @@ func (a *API) FetchBrokerByCID(cid CIDType) (*Broker, error) {
} }
// FetchBrokerListByTag return list of brokers with a specific tag // FetchBrokerListByTag return list of brokers with a specific tag
func (a *API) FetchBrokerListByTag(searchTag TagType) ([]Broker, error) { func (a *API) FetchBrokerListByTag(searchTag SearchTagType) ([]Broker, error) {
query := SearchQueryType(fmt.Sprintf("f__tags_has=%s", strings.Replace(strings.Join(searchTag, ","), ",", "&f__tags_has=", -1))) query := SearchQueryType(fmt.Sprintf("f__tags_has=%s", searchTag))
return a.BrokerSearch(query) return a.BrokerSearch(query)
} }
@ -73,9 +66,7 @@ func (a *API) BrokerSearch(query SearchQueryType) ([]Broker, error) {
} }
var brokers []Broker var brokers []Broker
if err := json.Unmarshal(result, &brokers); err != nil { json.Unmarshal(result, &brokers)
return nil, err
}
return brokers, nil return brokers, nil
} }
@ -88,9 +79,7 @@ func (a *API) FetchBrokerList() ([]Broker, error) {
} }
var response []Broker var response []Broker
if err := json.Unmarshal(result, &response); err != nil { json.Unmarshal(result, &response)
return nil, err
}
return response, nil return response, nil
} }

View File

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

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

View File

@ -1,7 +1,3 @@
// 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 package checkmgr
import ( import (
@ -10,7 +6,6 @@ import (
"net" "net"
"net/url" "net/url"
"reflect" "reflect"
"strconv"
"strings" "strings"
"time" "time"
@ -80,7 +75,7 @@ func (cm *CheckManager) selectBroker() (*api.Broker, error) {
var brokerList []api.Broker var brokerList []api.Broker
var err error var err error
if len(cm.brokerSelectTag) > 0 { if cm.brokerSelectTag != "" {
brokerList, err = cm.apih.FetchBrokerListByTag(cm.brokerSelectTag) brokerList, err = cm.apih.FetchBrokerListByTag(cm.brokerSelectTag)
if err != nil { if err != nil {
return nil, err return nil, err
@ -146,10 +141,10 @@ func (cm *CheckManager) brokerSupportsCheckType(checkType CheckTypeType, details
// Is the broker valid (active, supports check type, and reachable) // Is the broker valid (active, supports check type, and reachable)
func (cm *CheckManager) isValidBroker(broker *api.Broker) bool { func (cm *CheckManager) isValidBroker(broker *api.Broker) bool {
brokerHost := "" brokerPort := 0
brokerPort := ""
valid := false valid := false
for _, detail := range broker.Details { for _, detail := range broker.Details {
brokerPort = 43191
// broker must be active // broker must be active
if detail.Status != statusActive { if detail.Status != statusActive {
@ -167,24 +162,8 @@ func (cm *CheckManager) isValidBroker(broker *api.Broker) bool {
continue continue
} }
if detail.ExternalPort != 0 {
brokerPort = strconv.Itoa(detail.ExternalPort)
} else {
if detail.Port != 0 {
brokerPort = strconv.Itoa(detail.Port)
} else {
brokerPort = "43191"
}
}
if detail.ExternalHost != "" {
brokerHost = detail.ExternalHost
} else {
brokerHost = detail.IP
}
// broker must be reachable and respond within designated time // broker must be reachable and respond within designated time
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", brokerHost, brokerPort), cm.brokerMaxResponseTime) conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", detail.IP, brokerPort), cm.brokerMaxResponseTime)
if err != nil { if err != nil {
if detail.CN != "trap.noit.circonus.net" { if detail.CN != "trap.noit.circonus.net" {
if cm.Debug { if cm.Debug {
@ -193,8 +172,8 @@ func (cm *CheckManager) isValidBroker(broker *api.Broker) bool {
continue // not able to reach the broker (or respone slow enough for it to be considered not usable) 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 circonus trap broker, try port 443
brokerPort = "443" brokerPort = 443
conn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%s", detail.CN, brokerPort), cm.brokerMaxResponseTime) conn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", detail.CN, brokerPort), cm.brokerMaxResponseTime)
if err != nil { if err != nil {
if cm.Debug { if cm.Debug {
cm.Log.Printf("[DEBUG] Broker '%s' unable to connect %v\n", broker.Name, err) cm.Log.Printf("[DEBUG] Broker '%s' unable to connect %v\n", broker.Name, err)

View File

@ -1,7 +1,3 @@
// 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 package checkmgr
import ( import (
@ -74,7 +70,8 @@ func (cm *CheckManager) fetchCert() ([]byte, error) {
} }
cadata := new(CACert) cadata := new(CACert)
if err := json.Unmarshal(response, cadata); err != nil { err = json.Unmarshal(response, cadata)
if err != nil {
return nil, err return nil, err
} }

View File

@ -1,7 +1,3 @@
// 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 package checkmgr
import ( import (
@ -17,68 +13,6 @@ import (
"github.com/circonus-labs/circonus-gometrics/api" "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
}
// only if there is *something* to update
if !cm.forceCheckUpdate && len(newMetrics) == 0 && len(cm.metricTags) == 0 {
return
}
// refresh check bundle (in case there were changes made by other apps or in UI)
checkBundle, err := cm.apih.FetchCheckBundleByCID(api.CIDType(cm.checkBundle.Cid))
if err != nil {
cm.Log.Printf("[ERROR] unable to fetch up-to-date check bundle %v", err)
return
}
cm.cbmu.Lock()
cm.checkBundle = checkBundle
cm.cbmu.Unlock()
cm.addNewMetrics(newMetrics)
if len(cm.metricTags) > 0 {
// note: if a tag has been added (queued) for a metric which never gets sent
// the tags will be discarded. (setting tags does not *create* metrics.)
for metricName, metricTags := range cm.metricTags {
for metricIdx, metric := range cm.checkBundle.Metrics {
if metric.Name == metricName {
cm.checkBundle.Metrics[metricIdx].Tags = metricTags
break
}
}
cm.mtmu.Lock()
delete(cm.metricTags, metricName)
cm.mtmu.Unlock()
}
cm.forceCheckUpdate = true
}
if cm.forceCheckUpdate {
newCheckBundle, err := cm.apih.UpdateCheckBundle(cm.checkBundle)
if err != nil {
cm.Log.Printf("[ERROR] updating check bundle %v", err)
return
}
cm.forceCheckUpdate = false
cm.cbmu.Lock()
cm.checkBundle = newCheckBundle
cm.cbmu.Unlock()
cm.inventoryMetrics()
}
}
// Initialize CirconusMetrics instance. Attempt to find a check otherwise create one. // Initialize CirconusMetrics instance. Attempt to find a check otherwise create one.
// use cases: // use cases:
// //
@ -94,8 +28,6 @@ func (cm *CheckManager) initializeTrapURL() error {
cm.trapmu.Lock() cm.trapmu.Lock()
defer cm.trapmu.Unlock() 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.checkSubmissionURL != "" {
if !cm.enabled { if !cm.enabled {
cm.trapURL = cm.checkSubmissionURL cm.trapURL = cm.checkSubmissionURL
@ -118,9 +50,6 @@ func (cm *CheckManager) initializeTrapURL() error {
if err != nil { if err != nil {
return err 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 // extract check id from check object returned from looking up using submission url
// set m.CheckId to the id // set m.CheckId to the id
// set m.SubmissionUrl to "" to prevent trying to search on it going forward // set m.SubmissionUrl to "" to prevent trying to search on it going forward
@ -142,13 +71,10 @@ func (cm *CheckManager) initializeTrapURL() error {
if err != nil { if err != nil {
return err return err
} }
if !check.Active {
return fmt.Errorf("[ERROR] Check ID %v is not active", check.Cid)
}
} else { } else {
searchCriteria := fmt.Sprintf( searchCriteria := fmt.Sprintf(
"(active:1)(host:\"%s\")(type:\"%s\")(tags:%s)(notes:%s)", "(active:1)(host:\"%s\")(type:\"%s\")(tags:%s)",
cm.checkTarget, cm.checkType, strings.Join(cm.checkSearchTag, ","), fmt.Sprintf("cgm_instanceid=%s", cm.checkInstanceID)) cm.checkInstanceID, cm.checkType, cm.checkSearchTag)
checkBundle, err = cm.checkBundleSearch(searchCriteria) checkBundle, err = cm.checkBundleSearch(searchCriteria)
if err != nil { if err != nil {
return err return err
@ -186,19 +112,8 @@ func (cm *CheckManager) initializeTrapURL() error {
cm.checkBundle = checkBundle cm.checkBundle = checkBundle
cm.inventoryMetrics() cm.inventoryMetrics()
// determine the trap url to which metrics should be PUT // url to which metrics should be PUT
if checkBundle.Type == "httptrap" { cm.trapURL = api.URLType(checkBundle.Config.SubmissionURL)
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 // 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) // (cert created with server name as CN but IP used in trap url)
@ -263,11 +178,11 @@ func (cm *CheckManager) createNewCheck() (*api.CheckBundle, *api.Broker, error)
DisplayName: string(cm.checkDisplayName), DisplayName: string(cm.checkDisplayName),
Metrics: []api.CheckBundleMetric{}, Metrics: []api.CheckBundleMetric{},
MetricLimit: 0, MetricLimit: 0,
Notes: fmt.Sprintf("cgm_instanceid=%s", cm.checkInstanceID), Notes: "",
Period: 60, Period: 60,
Status: statusActive, Status: statusActive,
Tags: append(cm.checkSearchTag, cm.checkTags...), Tags: append([]string{string(cm.checkSearchTag)}, cm.checkTags...),
Target: cm.checkTarget, Target: string(cm.checkInstanceID),
Timeout: 10, Timeout: 10,
Type: string(cm.checkType), Type: string(cm.checkType),
} }

View File

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

View File

@ -1,7 +1,3 @@
// 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 package checkmgr
import ( import (
@ -29,85 +25,44 @@ func (cm *CheckManager) ActivateMetric(name string) bool {
return false return false
} }
// AddMetricTags updates check bundle metrics with tags // AddNewMetrics updates a check bundle with new metrics
func (cm *CheckManager) AddMetricTags(metricName string, tags []string, appendTags bool) bool { func (cm *CheckManager) AddNewMetrics(newMetrics map[string]*api.CheckBundleMetric) {
tagsUpdated := false // only if check manager is enabled
if !cm.enabled {
if len(tags) == 0 { return
return tagsUpdated
} }
if _, exists := cm.metricTags[metricName]; !exists { // only if checkBundle has been populated
foundMetric := false if cm.checkBundle == nil {
return
for _, metric := range cm.checkBundle.Metrics {
if metric.Name == metricName {
foundMetric = true
cm.metricTags[metricName] = metric.Tags
break
}
}
if !foundMetric {
cm.metricTags[metricName] = []string{}
}
} }
action := "no new" newCheckBundle := cm.checkBundle
if appendTags { numCurrMetrics := len(newCheckBundle.Metrics)
numNewTags := countNewTags(cm.metricTags[metricName], tags)
if numNewTags > 0 {
action = "Added"
cm.metricTags[metricName] = append(cm.metricTags[metricName], tags...)
tagsUpdated = true
}
} else {
action = "Set"
cm.metricTags[metricName] = tags
tagsUpdated = true
}
if cm.Debug {
cm.Log.Printf("[DEBUG] %s metric tag(s) %s %v\n", action, metricName, tags)
}
return tagsUpdated
}
// addNewMetrics updates a check bundle with new metrics
func (cm *CheckManager) addNewMetrics(newMetrics map[string]*api.CheckBundleMetric) bool {
updatedCheckBundle := false
if cm.checkBundle == nil || len(newMetrics) == 0 {
return updatedCheckBundle
}
cm.cbmu.Lock()
defer cm.cbmu.Unlock()
numCurrMetrics := len(cm.checkBundle.Metrics)
numNewMetrics := len(newMetrics) numNewMetrics := len(newMetrics)
if numCurrMetrics+numNewMetrics >= cap(cm.checkBundle.Metrics) { if numCurrMetrics+numNewMetrics >= cap(newCheckBundle.Metrics) {
nm := make([]api.CheckBundleMetric, numCurrMetrics+numNewMetrics) nm := make([]api.CheckBundleMetric, numCurrMetrics+numNewMetrics)
copy(nm, cm.checkBundle.Metrics) copy(nm, newCheckBundle.Metrics)
cm.checkBundle.Metrics = nm newCheckBundle.Metrics = nm
} }
cm.checkBundle.Metrics = cm.checkBundle.Metrics[0 : numCurrMetrics+numNewMetrics] newCheckBundle.Metrics = newCheckBundle.Metrics[0 : numCurrMetrics+numNewMetrics]
i := 0 i := 0
for _, metric := range newMetrics { for _, metric := range newMetrics {
cm.checkBundle.Metrics[numCurrMetrics+i] = *metric newCheckBundle.Metrics[numCurrMetrics+i] = *metric
i++ i++
updatedCheckBundle = true
} }
if updatedCheckBundle { checkBundle, err := cm.apih.UpdateCheckBundle(newCheckBundle)
cm.forceCheckUpdate = true if err != nil {
cm.Log.Printf("[ERROR] updating check bundle with new metrics %v", err)
return
} }
return updatedCheckBundle cm.checkBundle = checkBundle
cm.inventoryMetrics()
} }
// inventoryMetrics creates list of active metrics in check bundle // inventoryMetrics creates list of active metrics in check bundle
@ -118,31 +73,3 @@ func (cm *CheckManager) inventoryMetrics() {
} }
cm.availableMetrics = availableMetrics 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,7 +1,3 @@
// 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 // Package circonusgometrics provides instrumentation for your applications in the form
// of counters, gauges and histograms and allows you to publish them to // of counters, gauges and histograms and allows you to publish them to
// Circonus // Circonus
@ -34,7 +30,6 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"strconv"
"sync" "sync"
"time" "time"
@ -48,12 +43,8 @@ const (
// Config options for circonus-gometrics // Config options for circonus-gometrics
type Config struct { type Config struct {
Log *log.Logger Log *log.Logger
Debug bool 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 // API, Check and Broker configuration options
CheckManager checkmgr.Config CheckManager checkmgr.Config
@ -64,16 +55,12 @@ type Config struct {
// CirconusMetrics state // CirconusMetrics state
type CirconusMetrics struct { type CirconusMetrics struct {
Log *log.Logger Log *log.Logger
Debug bool Debug bool
resetCounters bool flushInterval time.Duration
resetGauges bool flushing bool
resetHistograms bool flushmu sync.Mutex
resetText bool check *checkmgr.CheckManager
flushInterval time.Duration
flushing bool
flushmu sync.Mutex
check *checkmgr.CheckManager
counters map[string]uint64 counters map[string]uint64
cm sync.Mutex cm sync.Mutex
@ -115,10 +102,12 @@ func NewCirconusMetrics(cfg *Config) (*CirconusMetrics, error) {
} }
cm.Debug = cfg.Debug cm.Debug = cfg.Debug
cm.Log = cfg.Log if cm.Debug {
if cfg.Log == nil {
if cm.Debug && cfg.Log == nil { cm.Log = log.New(os.Stderr, "", log.LstdFlags)
cm.Log = log.New(os.Stderr, "", log.LstdFlags) } else {
cm.Log = cfg.Log
}
} }
if cm.Log == nil { if cm.Log == nil {
cm.Log = log.New(ioutil.Discard, "", log.LstdFlags) cm.Log = log.New(ioutil.Discard, "", log.LstdFlags)
@ -135,36 +124,6 @@ func NewCirconusMetrics(cfg *Config) (*CirconusMetrics, error) {
} }
cm.flushInterval = dur 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.Debug = cm.Debug
cfg.CheckManager.Log = cm.Log cfg.CheckManager.Log = cm.Log
@ -283,13 +242,7 @@ func (m *CirconusMetrics) Flush() {
} }
} }
if len(output) > 0 { m.submit(output, newMetrics)
m.submit(output, newMetrics)
} else {
if m.Debug {
m.Log.Println("[DEBUG] No metrics to send, skipping")
}
}
m.flushmu.Lock() m.flushmu.Lock()
m.flushing = false m.flushing = false

View File

@ -1,7 +1,3 @@
// 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 package circonusgometrics
// A Counter is a monotonically increasing unsigned integer. // A Counter is a monotonically increasing unsigned integer.

View File

@ -1,7 +1,3 @@
// 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 package circonusgometrics
// A Gauge is an instantaneous measurement of a value. // A Gauge is an instantaneous measurement of a value.

View File

@ -1,7 +1,3 @@
// 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 package circonusgometrics
import ( import (
@ -29,20 +25,19 @@ func (m *CirconusMetrics) RecordValue(metric string, val float64) {
// SetHistogramValue adds a value to a histogram // SetHistogramValue adds a value to a histogram
func (m *CirconusMetrics) SetHistogramValue(metric string, val float64) { func (m *CirconusMetrics) SetHistogramValue(metric string, val float64) {
hist := m.NewHistogram(metric) m.NewHistogram(metric)
m.hm.Lock() m.histograms[metric].rw.Lock()
hist.rw.Lock() defer m.histograms[metric].rw.Unlock()
hist.hist.RecordValue(val)
hist.rw.Unlock() m.histograms[metric].hist.RecordValue(val)
m.hm.Unlock()
} }
// RemoveHistogram removes a histogram // RemoveHistogram removes a histogram
func (m *CirconusMetrics) RemoveHistogram(metric string) { func (m *CirconusMetrics) RemoveHistogram(metric string) {
m.hm.Lock() m.hm.Lock()
defer m.hm.Unlock()
delete(m.histograms, metric) delete(m.histograms, metric)
m.hm.Unlock()
} }
// NewHistogram returns a histogram instance. // NewHistogram returns a histogram instance.
@ -72,6 +67,7 @@ func (h *Histogram) Name() string {
// RecordValue records the given value to a histogram instance // RecordValue records the given value to a histogram instance
func (h *Histogram) RecordValue(v float64) { func (h *Histogram) RecordValue(v float64) {
h.rw.Lock() h.rw.Lock()
defer h.rw.Unlock()
h.hist.RecordValue(v) h.hist.RecordValue(v)
h.rw.Unlock()
} }

View File

@ -1,15 +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 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,14 +1,9 @@
// 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 package circonusgometrics
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net" "net"
@ -21,9 +16,9 @@ import (
) )
func (m *CirconusMetrics) submit(output map[string]interface{}, newMetrics map[string]*api.CheckBundleMetric) { func (m *CirconusMetrics) submit(output map[string]interface{}, newMetrics map[string]*api.CheckBundleMetric) {
if len(newMetrics) > 0 {
// update check if there are any new metrics or, if metric tags have been added since last submit m.check.AddNewMetrics(newMetrics)
m.check.UpdateCheck(newMetrics) }
str, err := json.Marshal(output) str, err := json.Marshal(output)
if err != nil { if err != nil {
@ -54,32 +49,8 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "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() client := retryablehttp.NewClient()
if trap.URL.Scheme == "https" { if trap.URL.Scheme == "https" {
client.HTTPClient.Transport = &http.Transport{ client.HTTPClient.Transport = &http.Transport{
@ -107,17 +78,10 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
DisableCompression: true, DisableCompression: true,
} }
} }
client.RetryWaitMin = 1 * time.Second client.RetryWaitMin = 10 * time.Millisecond
client.RetryWaitMax = 5 * time.Second client.RetryWaitMax = 50 * time.Millisecond
client.RetryMax = 3 client.RetryMax = 3
// retryablehttp only groks log or no log client.Logger = m.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 attempts := -1
client.RequestLogHook = func(logger *log.Logger, req *http.Request, retryNumber int) { client.RequestLogHook = func(logger *log.Logger, req *http.Request, retryNumber int) {
@ -126,9 +90,6 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
if lastHTTPError != nil {
return 0, fmt.Errorf("[ERROR] submitting: %+v %+v", err, lastHTTPError)
}
if attempts == client.RetryMax { if attempts == client.RetryMax {
m.check.RefreshTrap() m.check.RefreshTrap()
} }
@ -142,8 +103,9 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
} }
var response map[string]interface{} var response map[string]interface{}
if err := json.Unmarshal(body, &response); err != nil { err = json.Unmarshal(body, &response)
m.Log.Printf("[ERROR] parsing body, proceeding. %v (%s)\n", err, body) if err != nil {
m.Log.Printf("[ERROR] parsing body, proceeding. %s\n", err)
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {

View File

@ -1,7 +1,3 @@
// 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 package circonusgometrics
// A Text metric is an arbitrary string // A Text metric is an arbitrary string

View File

@ -1,7 +1,3 @@
// 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 package circonusgometrics
import ( import (

View File

@ -1,7 +1,3 @@
// 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 package circonusgometrics
import ( import (
@ -84,9 +80,7 @@ func (m *CirconusMetrics) snapshot() (c map[string]uint64, g map[string]string,
h = make(map[string]*circonusllhist.Histogram, len(m.histograms)) h = make(map[string]*circonusllhist.Histogram, len(m.histograms))
for n, hist := range m.histograms { for n, hist := range m.histograms {
hist.rw.Lock()
h[n] = hist.hist.CopyAndReset() h[n] = hist.hist.CopyAndReset()
hist.rw.Unlock()
} }
t = make(map[string]string, len(m.text)+len(m.textFuncs)) t = make(map[string]string, len(m.text)+len(m.textFuncs))
@ -98,24 +92,5 @@ func (m *CirconusMetrics) snapshot() (c map[string]uint64, g map[string]string,
t[n] = f() 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 return
} }

View File

@ -15,7 +15,7 @@ import (
) )
const ( const (
DEFAULT_HIST_SIZE = uint16(100) DEFAULT_HIST_SIZE = int16(100)
) )
var power_of_ten = [...]float64{ var power_of_ten = [...]float64{
@ -70,14 +70,6 @@ func NewBinFromFloat64(d float64) *Bin {
hb.SetFromFloat64(d) hb.SetFromFloat64(d)
return hb 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 { func (hb *Bin) SetFromFloat64(d float64) *Bin {
hb.val = -1 hb.val = -1
if math.IsInf(d, 0) || math.IsNaN(d) { if math.IsInf(d, 0) || math.IsNaN(d) {
@ -125,7 +117,10 @@ func (hb *Bin) SetFromFloat64(d float64) *Bin {
return hb return hb
} }
func (hb *Bin) PowerOfTen() float64 { func (hb *Bin) PowerOfTen() float64 {
idx := int(uint8(hb.exp)) idx := int(hb.exp)
if idx < 0 {
idx = 256 + idx
}
return power_of_ten[idx] return power_of_ten[idx]
} }
@ -188,58 +183,69 @@ func (hb *Bin) Left() float64 {
} }
func (h1 *Bin) Compare(h2 *Bin) int { func (h1 *Bin) Compare(h2 *Bin) int {
var v1, v2 int if h1.val == h2.val && h1.exp == h2.exp {
return 0
// 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 h2.val >= 0 { if h1.val == -1 {
v2 = ((int(h2.exp)+256)<<8)*int(((h2.val|(^h2.val+1))>>8)&1) + int(h2.val) return 1
} else {
v2 = ((int(h2.exp)+256)<<8)*int(((h2.val|(^h2.val+1))>>8)&1) - int(h2.val)
} }
if h2.val == -1 {
// return the difference return -1
return v2 - v1 }
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
} }
// This histogram structure tracks values are two decimal digits of precision // This histogram structure tracks values are two decimal digits of precision
// with a bounded error that remains bounded upon composition // with a bounded error that remains bounded upon composition
type Histogram struct { type Histogram struct {
mutex sync.Mutex
bvs []Bin bvs []Bin
used uint16 used int16
allocd uint16 allocd int16
lookup [256][]uint16
mutex sync.Mutex
useLocks bool
} }
// New returns a new Histogram // New returns a new Histogram
func New() *Histogram { func New() *Histogram {
return &Histogram{ return &Histogram{
allocd: DEFAULT_HIST_SIZE, allocd: DEFAULT_HIST_SIZE,
used: 0, used: 0,
bvs: make([]Bin, DEFAULT_HIST_SIZE), 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,
} }
} }
@ -260,24 +266,9 @@ func (h *Histogram) Mean() float64 {
// Reset forgets all bins in the histogram (they remain allocated) // Reset forgets all bins in the histogram (they remain allocated)
func (h *Histogram) Reset() { func (h *Histogram) Reset() {
if h.useLocks { h.mutex.Lock()
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.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 // RecordValue records the given value, returning an error if the value is out
@ -313,19 +304,13 @@ func (h *Histogram) RecordCorrectedValue(v, expectedInterval int64) error {
} }
// find where a new bin should go // find where a new bin should go
func (h *Histogram) InternalFind(hb *Bin) (bool, uint16) { func (h *Histogram) InternalFind(hb *Bin) (bool, int16) {
if h.used == 0 { if h.used == 0 {
return false, 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 rv := -1
idx := uint16(0) idx := int16(0)
l := uint16(0) l := int16(0)
r := h.used - 1 r := h.used - 1
for l < r { for l < r {
check := (r + l) / 2 check := (r + l) / 2
@ -354,9 +339,10 @@ func (h *Histogram) InternalFind(hb *Bin) (bool, uint16) {
} }
func (h *Histogram) InsertBin(hb *Bin, count int64) uint64 { func (h *Histogram) InsertBin(hb *Bin, count int64) uint64 {
if h.useLocks { h.mutex.Lock()
h.mutex.Lock() defer h.mutex.Unlock()
defer h.mutex.Unlock() if count == 0 {
return 0
} }
found, idx := h.InternalFind(hb) found, idx := h.InternalFind(hb)
if !found { if !found {
@ -377,20 +363,13 @@ func (h *Histogram) InsertBin(hb *Bin, count int64) uint64 {
h.bvs[idx].exp = hb.exp h.bvs[idx].exp = hb.exp
h.bvs[idx].count = uint64(count) h.bvs[idx].count = uint64(count)
h.used++ 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 return h.bvs[idx].count
} }
var newval uint64 var newval uint64
if count >= 0 { if count < 0 {
newval = h.bvs[idx].count + uint64(count)
} else {
newval = h.bvs[idx].count - uint64(-count) newval = h.bvs[idx].count - uint64(-count)
} else {
newval = h.bvs[idx].count + uint64(count)
} }
if newval < h.bvs[idx].count { //rolled if newval < h.bvs[idx].count { //rolled
newval = ^uint64(0) newval = ^uint64(0)
@ -399,39 +378,6 @@ func (h *Histogram) InsertBin(hb *Bin, count int64) uint64 {
return newval - h.bvs[idx].count 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 // RecordValues records n occurrences of the given value, returning an error if
// the value is out of range. // the value is out of range.
func (h *Histogram) RecordValues(v float64, n int64) error { func (h *Histogram) RecordValues(v float64, n int64) error {
@ -443,13 +389,11 @@ func (h *Histogram) RecordValues(v float64, n int64) error {
// Approximate mean // Approximate mean
func (h *Histogram) ApproxMean() float64 { func (h *Histogram) ApproxMean() float64 {
if h.useLocks { h.mutex.Lock()
h.mutex.Lock() defer h.mutex.Unlock()
defer h.mutex.Unlock()
}
divisor := 0.0 divisor := 0.0
sum := 0.0 sum := 0.0
for i := uint16(0); i < h.used; i++ { for i := int16(0); i < h.used; i++ {
midpoint := h.bvs[i].Midpoint() midpoint := h.bvs[i].Midpoint()
cardinality := float64(h.bvs[i].count) cardinality := float64(h.bvs[i].count)
divisor += cardinality divisor += cardinality
@ -463,12 +407,10 @@ func (h *Histogram) ApproxMean() float64 {
// Approximate sum // Approximate sum
func (h *Histogram) ApproxSum() float64 { func (h *Histogram) ApproxSum() float64 {
if h.useLocks { h.mutex.Lock()
h.mutex.Lock() defer h.mutex.Unlock()
defer h.mutex.Unlock()
}
sum := 0.0 sum := 0.0
for i := uint16(0); i < h.used; i++ { for i := int16(0); i < h.used; i++ {
midpoint := h.bvs[i].Midpoint() midpoint := h.bvs[i].Midpoint()
cardinality := float64(h.bvs[i].count) cardinality := float64(h.bvs[i].count)
sum += midpoint * cardinality sum += midpoint * cardinality
@ -477,12 +419,10 @@ func (h *Histogram) ApproxSum() float64 {
} }
func (h *Histogram) ApproxQuantile(q_in []float64) ([]float64, error) { func (h *Histogram) ApproxQuantile(q_in []float64) ([]float64, error) {
if h.useLocks { h.mutex.Lock()
h.mutex.Lock() defer h.mutex.Unlock()
defer h.mutex.Unlock()
}
q_out := make([]float64, len(q_in)) q_out := make([]float64, len(q_in))
i_q, i_b := 0, uint16(0) i_q, i_b := 0, int16(0)
total_cnt, bin_width, bin_left, lower_cnt, upper_cnt := 0.0, 0.0, 0.0, 0.0, 0.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 { if len(q_in) == 0 {
return q_out, nil return q_out, nil
@ -545,10 +485,8 @@ func (h *Histogram) ApproxQuantile(q_in []float64) ([]float64, error) {
// ValueAtQuantile returns the recorded value at the given quantile (0..1). // ValueAtQuantile returns the recorded value at the given quantile (0..1).
func (h *Histogram) ValueAtQuantile(q float64) float64 { func (h *Histogram) ValueAtQuantile(q float64) float64 {
if h.useLocks { h.mutex.Lock()
h.mutex.Lock() defer h.mutex.Unlock()
defer h.mutex.Unlock()
}
q_in := make([]float64, 1) q_in := make([]float64, 1)
q_in[0] = q q_in[0] = q
q_out, err := h.ApproxQuantile(q_in) q_out, err := h.ApproxQuantile(q_in)
@ -567,20 +505,16 @@ func (h *Histogram) SignificantFigures() int64 {
// Equals returns true if the two Histograms are equivalent, false if not. // Equals returns true if the two Histograms are equivalent, false if not.
func (h *Histogram) Equals(other *Histogram) bool { func (h *Histogram) Equals(other *Histogram) bool {
if h.useLocks { h.mutex.Lock()
h.mutex.Lock() other.mutex.Lock()
defer h.mutex.Unlock() defer h.mutex.Unlock()
} defer other.mutex.Unlock()
if other.useLocks {
other.mutex.Lock()
defer other.mutex.Unlock()
}
switch { switch {
case case
h.used != other.used: h.used != other.used:
return false return false
default: default:
for i := uint16(0); i < h.used; i++ { for i := int16(0); i < h.used; i++ {
if h.bvs[i].Compare(&other.bvs[i]) != 0 { if h.bvs[i].Compare(&other.bvs[i]) != 0 {
return false return false
} }
@ -593,10 +527,8 @@ func (h *Histogram) Equals(other *Histogram) bool {
} }
func (h *Histogram) CopyAndReset() *Histogram { func (h *Histogram) CopyAndReset() *Histogram {
if h.useLocks { h.mutex.Lock()
h.mutex.Lock() defer h.mutex.Unlock()
defer h.mutex.Unlock()
}
newhist := &Histogram{ newhist := &Histogram{
allocd: h.allocd, allocd: h.allocd,
used: h.used, used: h.used,
@ -605,20 +537,11 @@ func (h *Histogram) CopyAndReset() *Histogram {
h.allocd = DEFAULT_HIST_SIZE h.allocd = DEFAULT_HIST_SIZE
h.bvs = make([]Bin, DEFAULT_HIST_SIZE) h.bvs = make([]Bin, DEFAULT_HIST_SIZE)
h.used = 0 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 return newhist
} }
func (h *Histogram) DecStrings() []string { func (h *Histogram) DecStrings() []string {
if h.useLocks { h.mutex.Lock()
h.mutex.Lock() defer h.mutex.Unlock()
defer h.mutex.Unlock()
}
out := make([]string, h.used) out := make([]string, h.used)
for i, bin := range h.bvs[0:h.used] { for i, bin := range h.bvs[0:h.used] {
var buffer bytes.Buffer var buffer bytes.Buffer

View File

@ -32,16 +32,12 @@ import (
var ( var (
// Default retry configuration // Default retry configuration
defaultRetryWaitMin = 1 * time.Second defaultRetryWaitMin = 1 * time.Second
defaultRetryWaitMax = 30 * time.Second defaultRetryWaitMax = 5 * time.Minute
defaultRetryMax = 4 defaultRetryMax = 32
// defaultClient is used for performing requests without explicitly making // defaultClient is used for performing requests without explicitly making
// a new client. It is purposely private to avoid modifications. // a new client. It is purposely private to avoid modifications.
defaultClient = NewClient() defaultClient = NewClient()
// We need to consume response bodies to maintain http connections, but
// limit the size we consume to respReadLimit.
respReadLimit = int64(4096)
) )
// LenReader is an interface implemented by many in-memory io.Reader's. Used // LenReader is an interface implemented by many in-memory io.Reader's. Used
@ -97,16 +93,6 @@ type RequestLogHook func(*log.Logger, *http.Request, int)
// from this method, this will affect the response returned from Do(). // from this method, this will affect the response returned from Do().
type ResponseLogHook func(*log.Logger, *http.Response) type ResponseLogHook func(*log.Logger, *http.Response)
// CheckRetry specifies a policy for handling retries. It is called
// following each request with the response and error values returned by
// the http.Client. If CheckRetry returns false, the Client stops retrying
// and returns the response to the caller. If CheckRetry returns an error,
// that error value is returned in lieu of the error from the request. The
// Client will close any response body when retrying, but if the retry is
// aborted it is up to the CheckResponse callback to properly close any
// response body before returning.
type CheckRetry func(resp *http.Response, err error) (bool, error)
// Client is used to make HTTP requests. It adds additional functionality // Client is used to make HTTP requests. It adds additional functionality
// like automatic retries to tolerate minor outages. // like automatic retries to tolerate minor outages.
type Client struct { type Client struct {
@ -124,10 +110,6 @@ type Client struct {
// ResponseLogHook allows a user-supplied function to be called // ResponseLogHook allows a user-supplied function to be called
// with the response from each HTTP request executed. // with the response from each HTTP request executed.
ResponseLogHook ResponseLogHook ResponseLogHook ResponseLogHook
// CheckRetry specifies the policy for handling retries, and is called
// after each request. The default policy is DefaultRetryPolicy.
CheckRetry CheckRetry
} }
// NewClient creates a new Client with default settings. // NewClient creates a new Client with default settings.
@ -138,27 +120,9 @@ func NewClient() *Client {
RetryWaitMin: defaultRetryWaitMin, RetryWaitMin: defaultRetryWaitMin,
RetryWaitMax: defaultRetryWaitMax, RetryWaitMax: defaultRetryWaitMax,
RetryMax: defaultRetryMax, RetryMax: defaultRetryMax,
CheckRetry: DefaultRetryPolicy,
} }
} }
// DefaultRetryPolicy provides a default callback for Client.CheckRetry, which
// will retry on connection errors and server errors.
func DefaultRetryPolicy(resp *http.Response, err error) (bool, error) {
if err != nil {
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 {
return true, nil
}
return false, nil
}
// Do wraps calling an HTTP method with retries. // Do wraps calling an HTTP method with retries.
func (c *Client) Do(req *Request) (*http.Response, error) { func (c *Client) Do(req *Request) (*http.Response, error) {
c.Logger.Printf("[DEBUG] %s %s", req.Method, req.URL) c.Logger.Printf("[DEBUG] %s %s", req.Method, req.URL)
@ -179,34 +143,27 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
// Attempt the request // Attempt the request
resp, err := c.HTTPClient.Do(req.Request) resp, err := c.HTTPClient.Do(req.Request)
// Check if we should continue with retries.
checkOK, checkErr := c.CheckRetry(resp, err)
if err != nil { if err != nil {
c.Logger.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err) c.Logger.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err)
} else { goto RETRY
// Call this here to maintain the behavior of logging all requests, }
// even if CheckRetry signals to stop. code = resp.StatusCode
if c.ResponseLogHook != nil {
// Call the response logger function if provided. // Call the response logger function if provided.
c.ResponseLogHook(c.Logger, resp) if c.ResponseLogHook != nil {
} c.ResponseLogHook(c.Logger, resp)
} }
// Now decide if we should continue. // Check the response code. We retry on 500-range responses to allow
if !checkOK { // the server time to recover, as 500's are typically not permanent
if checkErr != nil { // errors and may relate to outages on the server side.
err = checkErr if code%500 < 100 {
} resp.Body.Close()
return resp, err goto RETRY
}
// We're going to retry, consume any response to reuse the connection.
if err == nil {
c.drainBody(resp.Body)
} }
return resp, nil
RETRY:
remain := c.RetryMax - i remain := c.RetryMax - i
if remain == 0 { if remain == 0 {
break break
@ -225,15 +182,6 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
req.Method, req.URL, c.RetryMax+1) req.Method, req.URL, c.RetryMax+1)
} }
// Try to read the response body so we can reuse this connection.
func (c *Client) drainBody(body io.ReadCloser) {
defer body.Close()
_, err := io.Copy(ioutil.Discard, io.LimitReader(body, respReadLimit))
if err != nil {
c.Logger.Printf("[ERR] error reading response body: %v", err)
}
}
// Get is a shortcut for doing a GET request without making a new client. // Get is a shortcut for doing a GET request without making a new client.
func Get(url string) (*http.Response, error) { func Get(url string) (*http.Response, error) {
return defaultClient.Get(url) return defaultClient.Get(url)

30
vendor/vendor.json vendored
View File

@ -220,28 +220,28 @@
"versionExact": "v1.2.1" "versionExact": "v1.2.1"
}, },
{ {
"checksumSHA1": "szvY4u7TlXkrQ3PW8wmyJaIFy0U=", "checksumSHA1": "b5zgHT9TxBAVh/KP9kQi7QVoz9w=",
"path": "github.com/circonus-labs/circonus-gometrics", "path": "github.com/circonus-labs/circonus-gometrics",
"revision": "d17a8420c36e800fcb46bbd4d2a15b93c68605ea", "revision": "a7c30e0dcc6e2341053132470dcedc12bc7705ef",
"revisionTime": "2016-11-09T19:23:37Z" "revisionTime": "2016-07-22T17:27:10Z"
}, },
{ {
"checksumSHA1": "WUE6oF152uN5FcLmmq+nO3Yl7pU=", "checksumSHA1": "IFiYTxu8jshL4A8BCttUaDhp1m4=",
"path": "github.com/circonus-labs/circonus-gometrics/api", "path": "github.com/circonus-labs/circonus-gometrics/api",
"revision": "d17a8420c36e800fcb46bbd4d2a15b93c68605ea", "revision": "a7c30e0dcc6e2341053132470dcedc12bc7705ef",
"revisionTime": "2016-11-09T19:23:37Z" "revisionTime": "2016-07-22T17:27:10Z"
}, },
{ {
"checksumSHA1": "beRBHHy2+V6Ht4cACIMmZOCnzgg=", "checksumSHA1": "+9vcRzlTdvEjH/Uf8fKC5MXdjNw=",
"path": "github.com/circonus-labs/circonus-gometrics/checkmgr", "path": "github.com/circonus-labs/circonus-gometrics/checkmgr",
"revision": "d17a8420c36e800fcb46bbd4d2a15b93c68605ea", "revision": "a7c30e0dcc6e2341053132470dcedc12bc7705ef",
"revisionTime": "2016-11-09T19:23:37Z" "revisionTime": "2016-07-22T17:27:10Z"
}, },
{ {
"checksumSHA1": "eYWKyMvUWZpmuXjDEIGy9lBddK0=", "checksumSHA1": "C4Z7+l5GOpOCW5DcvNYzheGvQRE=",
"path": "github.com/circonus-labs/circonusllhist", "path": "github.com/circonus-labs/circonusllhist",
"revision": "f3bb61c09a65a1bb5833219937fe4e7042dadab4", "revision": "d724266ae5270ae8b87a5d2e8081f04e307c3c18",
"revisionTime": "2016-11-09T20:01:07Z" "revisionTime": "2016-05-26T04:38:13Z"
}, },
{ {
"checksumSHA1": "fXAinpJ5bOcborK7AiO1rnW60BI=", "checksumSHA1": "fXAinpJ5bOcborK7AiO1rnW60BI=",
@ -392,10 +392,10 @@
"revisionTime": "2015-09-16T20:57:42Z" "revisionTime": "2015-09-16T20:57:42Z"
}, },
{ {
"checksumSHA1": "ErJHGU6AVPZM9yoY/xV11TwSjQs=", "checksumSHA1": "bfVGm7xZ2VFpddJp3KEPUZ8Y9Po=",
"path": "github.com/hashicorp/go-retryablehttp", "path": "github.com/hashicorp/go-retryablehttp",
"revision": "6e85be8fee1dcaa02c0eaaac2df5a8fbecf94145", "revision": "886ce0458bc81ccca0fb7044c1be0e9ab590bed7",
"revisionTime": "2016-09-30T03:51:02Z" "revisionTime": "2016-07-18T23:34:41Z"
}, },
{ {
"checksumSHA1": "xZ7Ban1x//6uUIU1xtrTbCYNHBc=", "checksumSHA1": "xZ7Ban1x//6uUIU1xtrTbCYNHBc=",

View File

@ -816,12 +816,6 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
* <a name="telemetry-circonus_check_search_tag"></a><a href="#telemetry-circonus_check_search_tag">`circonus_check_search_tag`</a> * <a name="telemetry-circonus_check_search_tag"></a><a href="#telemetry-circonus_check_search_tag">`circonus_check_search_tag`</a>
A special tag which, when coupled with the instance id, helps to narrow down the search results when neither a Submission URL or Check ID is provided. By default, this is set to service:application name (e.g. "service:consul"). A special tag which, when coupled with the instance id, helps to narrow down the search results when neither a Submission URL or Check ID is provided. By default, this is set to service:application name (e.g. "service:consul").
* <a name="telemetry-circonus_check_display_name"</a><a href="#telemetry-circonus_check_display_name">`circonus_check_display_name`</a>
Specifies a name to give a check when it is created. This name is displayed in the Circonus UI Checks list. Available in Consul 0.7.1 and later.
* <a name="telemetry-circonus_check_tags"</a><a href="telemetry-circonus_check_tags">`circonus_check_tags`</a>
Comma separated list of additional tags to add to a check when it is created. Available in Consul 0.7.1 and later.
* <a name="telemetry-circonus_broker_id"></a><a href="#telemetry-circonus_broker_id">`circonus_broker_id`</a> * <a name="telemetry-circonus_broker_id"></a><a href="#telemetry-circonus_broker_id">`circonus_broker_id`</a>
The ID of a specific Circonus Broker to use when creating a new check. The numeric portion of `broker._cid` field in a Broker API object. If metric management is enabled and neither a Submission URL nor Check ID is provided, an attempt will be made to search for an existing check using Instance ID and Search Tag. If one is not found, a new HTTPTRAP check will be created. By default, this is not used and a random Enterprise Broker is selected, or the default Circonus Public Broker. The ID of a specific Circonus Broker to use when creating a new check. The numeric portion of `broker._cid` field in a Broker API object. If metric management is enabled and neither a Submission URL nor Check ID is provided, an attempt will be made to search for an existing check using Instance ID and Search Tag. If one is not found, a new HTTPTRAP check will be created. By default, this is not used and a random Enterprise Broker is selected, or the default Circonus Public Broker.