2018-03-22 04:19:55 +08:00
package main
import (
2019-06-25 10:30:32 +08:00
"context"
2018-03-22 04:19:55 +08:00
"flag"
"fmt"
"log"
"os"
2019-06-25 10:30:32 +08:00
"os/signal"
2018-03-22 04:19:55 +08:00
"strconv"
2018-05-25 12:09:49 +08:00
"strings"
2019-06-25 10:30:32 +08:00
"syscall"
2018-03-22 04:19:55 +08:00
"time"
2019-10-15 02:24:14 +08:00
"github.com/gobwas/glob"
2018-03-22 04:19:55 +08:00
)
2020-02-19 23:14:22 +08:00
var defaultForceHighRes , _ = strconv . ParseBool ( os . Getenv ( "FORCE_HIGH_RES" ) )
2018-03-22 04:19:55 +08:00
var (
2019-10-10 22:14:29 +08:00
awsAccessKeyId = flag . String ( "aws_access_key_id" , os . Getenv ( "AWS_ACCESS_KEY_ID" ) , "AWS access key Id with permissions to publish CloudWatch metrics" )
awsSecretAccessKey = flag . String ( "aws_secret_access_key" , os . Getenv ( "AWS_SECRET_ACCESS_KEY" ) , "AWS secret access key with permissions to publish CloudWatch metrics" )
2019-12-03 10:36:53 +08:00
awsSessionToken = flag . String ( "aws_session_token" , os . Getenv ( "AWS_SESSION_TOKEN" ) , "AWS session token with permissions to publish CloudWatch metrics" )
2019-10-10 22:14:29 +08:00
cloudWatchNamespace = flag . String ( "cloudwatch_namespace" , os . Getenv ( "CLOUDWATCH_NAMESPACE" ) , "CloudWatch Namespace" )
cloudWatchRegion = flag . String ( "cloudwatch_region" , os . Getenv ( "CLOUDWATCH_REGION" ) , "CloudWatch Region" )
cloudWatchPublishTimeout = flag . String ( "cloudwatch_publish_timeout" , os . Getenv ( "CLOUDWATCH_PUBLISH_TIMEOUT" ) , "CloudWatch publish timeout in seconds" )
prometheusScrapeInterval = flag . String ( "prometheus_scrape_interval" , os . Getenv ( "PROMETHEUS_SCRAPE_INTERVAL" ) , "Prometheus scrape interval in seconds" )
prometheusScrapeUrl = flag . String ( "prometheus_scrape_url" , os . Getenv ( "PROMETHEUS_SCRAPE_URL" ) , "Prometheus scrape URL" )
certPath = flag . String ( "cert_path" , os . Getenv ( "CERT_PATH" ) , "Path to SSL Certificate file (when using SSL for `prometheus_scrape_url`)" )
keyPath = flag . String ( "key_path" , os . Getenv ( "KEY_PATH" ) , "Path to Key file (when using SSL for `prometheus_scrape_url`)" )
skipServerCertCheck = flag . String ( "accept_invalid_cert" , os . Getenv ( "ACCEPT_INVALID_CERT" ) , "Accept any certificate during TLS handshake. Insecure, use only for testing" )
additionalDimension = flag . String ( "additional_dimension" , os . Getenv ( "ADDITIONAL_DIMENSION" ) , "Additional dimension specified by NAME=VALUE" )
replaceDimensions = flag . String ( "replace_dimensions" , os . Getenv ( "REPLACE_DIMENSIONS" ) , "replace dimensions specified by NAME=VALUE,..." )
includeMetrics = flag . String ( "include_metrics" , os . Getenv ( "INCLUDE_METRICS" ) , "Only publish the specified metrics (comma-separated list of glob patterns, e.g. 'up,http_*')" )
excludeMetrics = flag . String ( "exclude_metrics" , os . Getenv ( "EXCLUDE_METRICS" ) , "Never publish the specified metrics (comma-separated list of glob patterns, e.g. 'tomcat_*')" )
2019-10-15 02:24:14 +08:00
includeDimensionsForMetrics = flag . String ( "include_dimensions_for_metrics" , os . Getenv ( "INCLUDE_DIMENSIONS_FOR_METRICS" ) , "Only publish the specified dimensions for metrics (semi-colon-separated key values of comma-separated dimensions of METRIC=dim1,dim2;, e.g. 'flink_jobmanager=job_id')" )
excludeDimensionsForMetrics = flag . String ( "exclude_dimensions_for_metrics" , os . Getenv ( "EXCLUDE_DIMENSIONS_FOR_METRICS" ) , "Never publish the specified dimensions for metrics (semi-colon-separated key values of comma-separated dimensions of METRIC=dim1,dim2;, e.g. 'flink_jobmanager=job,host;zk_up=host,pod;')" )
2020-02-19 23:14:22 +08:00
forceHighRes = flag . Bool ( "force_high_res" , defaultForceHighRes , "Publish all metrics with high resolution, even when original metrics don't have the label " + cwHighResLabel )
2018-03-22 04:19:55 +08:00
)
2019-10-10 22:14:29 +08:00
// kevValMustParse takes a string and exits with a message if it cannot parse as KEY=VALUE
func keyValMustParse ( str , message string ) ( string , string ) {
kv := strings . SplitN ( str , "=" , 2 )
if len ( kv ) != 2 {
log . Fatalf ( "prometheus-to-cloudwatch: Error: %s" , message )
}
return kv [ 0 ] , kv [ 1 ]
}
2019-10-15 02:24:14 +08:00
// dimensionMatcherListMustParse takes a string and a flag name and exists with a message
// if it cannot parse as GLOB=dim1,dim2;GLOB2=dim3
func dimensionMatcherListMustParse ( str , flag string ) [ ] MatcherWithStringSet {
var matcherList [ ] MatcherWithStringSet
// split metric1=dim1,dim2;metric2=dim1
// into [
// metric1=dim1,dim2
// metric*=dim1
// ]
// then into [{ Matcher: "metric1": Set: [dim1, dim2] } , { Matcher: "metric_*": Set: [dim1] }]
for _ , sublist := range strings . Split ( str , ";" ) {
key , val := keyValMustParse ( sublist , fmt . Sprintf ( "%s must be formatted as METRIC_NAME=DIM_LIST;..." , flag ) )
metricPattern , err := glob . Compile ( key )
if err != nil {
log . Fatal ( fmt . Errorf ( "prometheus-to-cloudwatch: Error: %s contains invalid glob pattern in '%s': %s" , flag , key , err ) )
}
dims := strings . Split ( val , "," )
if len ( dims ) == 0 {
log . Fatalf ( "prometheus-to-cloudwatch: Error: %s was not given dimensions to exclude for metric '%s'" , flag , key )
}
g := MatcherWithStringSet {
Matcher : metricPattern ,
Set : stringSliceToSet ( dims ) ,
}
matcherList = append ( matcherList , g )
}
return matcherList
}
2019-10-10 22:14:29 +08:00
// stringSliceToSet creates a "set" (a boolean map) from a slice of strings
func stringSliceToSet ( slice [ ] string ) StringSet {
boolMap := make ( StringSet , len ( slice ) )
for i := range slice {
boolMap [ slice [ i ] ] = true
}
return boolMap
}
2018-03-22 04:19:55 +08:00
func main ( ) {
flag . Parse ( )
if * cloudWatchNamespace == "" {
flag . PrintDefaults ( )
log . Fatal ( "prometheus-to-cloudwatch: Error: -cloudwatch_namespace or CLOUDWATCH_NAMESPACE required" )
}
if * cloudWatchRegion == "" {
flag . PrintDefaults ( )
log . Fatal ( "prometheus-to-cloudwatch: Error: -cloudwatch_region or CLOUDWATCH_REGION required" )
}
if * prometheusScrapeUrl == "" {
flag . PrintDefaults ( )
log . Fatal ( "prometheus-to-cloudwatch: Error: -prometheus_scrape_url or PROMETHEUS_SCRAPE_URL required" )
}
if ( * certPath != "" && * keyPath == "" ) || ( * certPath == "" && * keyPath != "" ) {
flag . PrintDefaults ( )
log . Fatal ( "prometheus-to-cloudwatch: Error: when using SSL, both -prometheus_cert_path and -prometheus_key_path are required. If not using SSL, do not provide any of them" )
}
var skipCertCheck = true
var err error
if * skipServerCertCheck != "" {
if skipCertCheck , err = strconv . ParseBool ( * skipServerCertCheck ) ; err != nil {
log . Fatal ( "prometheus-to-cloudwatch: Error: " , err )
}
}
2018-05-25 12:09:49 +08:00
var additionalDimensions = map [ string ] string { }
if * additionalDimension != "" {
2019-10-10 22:14:29 +08:00
key , val := keyValMustParse ( * additionalDimension , "-additionalDimension must be formatted as NAME=VALUE" )
additionalDimensions [ key ] = val
2018-05-25 12:09:49 +08:00
}
2019-02-27 03:28:42 +08:00
var replaceDims = map [ string ] string { }
if * replaceDimensions != "" {
kvs := strings . Split ( * replaceDimensions , "," )
if len ( kvs ) > 0 {
for _ , rd := range kvs {
2019-10-10 22:14:29 +08:00
key , val := keyValMustParse ( rd , "-replaceDimensions must be formatted as NAME=VALUE,..." )
replaceDims [ key ] = val
2019-02-27 03:28:42 +08:00
}
}
}
2019-06-21 23:50:30 +08:00
var includeMetricsList [ ] glob . Glob
if * includeMetrics != "" {
for _ , pattern := range strings . Split ( * includeMetrics , "," ) {
g , err := glob . Compile ( pattern )
if err != nil {
log . Fatal ( fmt . Errorf ( "prometheus-to-cloudwatch: Error: -include_metrics contains invalid glob pattern in '%s': %s" , pattern , err ) )
}
includeMetricsList = append ( includeMetricsList , g )
}
}
var excludeMetricsList [ ] glob . Glob
if * excludeMetrics != "" {
for _ , pattern := range strings . Split ( * excludeMetrics , "," ) {
g , err := glob . Compile ( pattern )
if err != nil {
log . Fatal ( fmt . Errorf ( "prometheus-to-cloudwatch: Error: -exclude_metrics contains invalid glob pattern in '%s': %s" , pattern , err ) )
}
excludeMetricsList = append ( excludeMetricsList , g )
}
}
2019-10-10 22:14:29 +08:00
var excludeDimensionsForMetricsList [ ] MatcherWithStringSet
if * excludeDimensionsForMetrics != "" {
2019-10-15 02:24:14 +08:00
excludeDimensionsForMetricsList = dimensionMatcherListMustParse ( * excludeDimensionsForMetrics , "-exclude_dimensions_for_metrics" )
}
2019-10-10 22:14:29 +08:00
2019-10-15 02:24:14 +08:00
var includeDimensionsForMetricsList [ ] MatcherWithStringSet
if * includeDimensionsForMetrics != "" {
includeDimensionsForMetricsList = dimensionMatcherListMustParse ( * includeDimensionsForMetrics , "-include_dimensions_for_metrics" )
2019-10-10 22:14:29 +08:00
}
2018-03-22 04:19:55 +08:00
config := & Config {
CloudWatchNamespace : * cloudWatchNamespace ,
CloudWatchRegion : * cloudWatchRegion ,
PrometheusScrapeUrl : * prometheusScrapeUrl ,
PrometheusCertPath : * certPath ,
PrometheusKeyPath : * keyPath ,
PrometheusSkipServerCertCheck : skipCertCheck ,
AwsAccessKeyId : * awsAccessKeyId ,
AwsSecretAccessKey : * awsSecretAccessKey ,
2019-12-03 10:36:53 +08:00
AwsSessionToken : * awsSessionToken ,
2018-05-25 12:09:49 +08:00
AdditionalDimensions : additionalDimensions ,
2019-02-27 03:28:42 +08:00
ReplaceDimensions : replaceDims ,
2019-06-21 23:50:30 +08:00
IncludeMetrics : includeMetricsList ,
ExcludeMetrics : excludeMetricsList ,
2019-10-10 22:14:29 +08:00
ExcludeDimensionsForMetrics : excludeDimensionsForMetricsList ,
2019-10-15 02:24:14 +08:00
IncludeDimensionsForMetrics : includeDimensionsForMetricsList ,
2020-02-19 23:14:22 +08:00
ForceHighRes : * forceHighRes ,
2018-03-22 04:19:55 +08:00
}
if * prometheusScrapeInterval != "" {
interval , err := strconv . Atoi ( * prometheusScrapeInterval )
if err != nil {
log . Fatal ( "prometheus-to-cloudwatch: error parsing 'prometheus_scrape_interval': " , err )
}
config . CloudWatchPublishInterval = time . Duration ( interval ) * time . Second
}
if * cloudWatchPublishTimeout != "" {
timeout , err := strconv . Atoi ( * cloudWatchPublishTimeout )
if err != nil {
log . Fatal ( "prometheus-to-cloudwatch: error parsing 'cloudwatch_publish_timeout': " , err )
}
config . CloudWatchPublishTimeout = time . Duration ( timeout ) * time . Second
}
bridge , err := NewBridge ( config )
if err != nil {
log . Fatal ( "prometheus-to-cloudwatch: Error: " , err )
}
2019-06-25 10:30:32 +08:00
log . Println ( "prometheus-to-cloudwatch: Starting prometheus-to-cloudwatch bridge" )
ctx := context . Background ( )
// trap Ctrl+C and call cancel on the context
ctx , cancel := context . WithCancel ( ctx )
signals := make ( chan os . Signal , 1 )
signal . Notify ( signals , syscall . SIGINT , syscall . SIGTERM )
defer func ( ) {
signal . Stop ( signals )
cancel ( )
} ( )
go func ( ) {
select {
case <- signals :
cancel ( )
case <- ctx . Done ( ) :
}
} ( )
bridge . Run ( ctx )
2018-03-22 04:19:55 +08:00
}