<commit_before>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package account

import (
	"time"

	"github.com/TheThingsNetwork/ttn/core/types"
)

// Application represents an application on The Things Network
type Application struct {
	ID            string            `json:"id"   valid:"required"`
	Name          string            `json:"name" valid:"required"`
	EUIs          []types.AppEUI    `json:"euis,omitempty"`
	AccessKeys    []types.AccessKey `json:"access_keys,omitempty"`
	Created       time.Time         `json:"created,omitempty"`
	Collaborators []Collaborator    `json:"collaborators,omitempty"`
}

// Collaborator is a user that has rights to a certain application
type Collaborator struct {
	Username string        `json:"username" valid:"required"`
	Rights   []types.Right `json:"rights"   valid:"required"`
}

// HasRight checks if the collaborator has a specific right
func (c *Collaborator) HasRight(right types.Right) bool {
	for _, r := range c.Rights {
		if r == right {
			return true
		}
	}
	return false
}

// Profile represents the profile of a user
type Profile struct {
	Username string `json:"username"`
	Email    string `json:"email"`
	Name     *Name  `json:"name"`
}

// Name represents the full name of a user
type Name struct {
	First string `json:"first"`
	Last  string `json:"last"`
}

// Component represents a component on the newtork
type Component struct {
	Type    string    `json:"type"`
	ID      string    `json:"id"`
	Created time.Time `json:"created,omitempty"`
}

// String implements the Stringer interface for Name
func (n *Name) String() string {
	return n.First + " " + n.Last
}

// Gateway represents a gateway on the account server
type Gateway struct {
	ID               string           `json:"id" valid:"required"`
	EUI              types.GatewayEUI `json:"eui" valid:"required"`
	Activated        bool             `json:"activated"`
	FrequencyPlan    string           `json:"frequency_plan"`
	FrequencyPlanURL string           `json:"frequency_plan_url"`
	LocationPublic   bool             `json:"location_public"`
	StatusPublic     bool             `json:"status_public"`
	Location         *Location        `json:"location"`
	Collaborators    []Collaborator   `json:"collaborator"`
	Key              string           `json:"key"`
}

type Location struct {
	Lng float64 `json:"lng"`
	Lat float64 `json:"lat"`
}
<commit_msg>Do not shorten field names in Location struct<commit_after>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package account

import (
	"time"

	"github.com/TheThingsNetwork/ttn/core/types"
)

// Application represents an application on The Things Network
type Application struct {
	ID            string            `json:"id"   valid:"required"`
	Name          string            `json:"name" valid:"required"`
	EUIs          []types.AppEUI    `json:"euis,omitempty"`
	AccessKeys    []types.AccessKey `json:"access_keys,omitempty"`
	Created       time.Time         `json:"created,omitempty"`
	Collaborators []Collaborator    `json:"collaborators,omitempty"`
}

// Collaborator is a user that has rights to a certain application
type Collaborator struct {
	Username string        `json:"username" valid:"required"`
	Rights   []types.Right `json:"rights"   valid:"required"`
}

// HasRight checks if the collaborator has a specific right
func (c *Collaborator) HasRight(right types.Right) bool {
	for _, r := range c.Rights {
		if r == right {
			return true
		}
	}
	return false
}

// Profile represents the profile of a user
type Profile struct {
	Username string `json:"username"`
	Email    string `json:"email"`
	Name     *Name  `json:"name"`
}

// Name represents the full name of a user
type Name struct {
	First string `json:"first"`
	Last  string `json:"last"`
}

// Component represents a component on the newtork
type Component struct {
	Type    string    `json:"type"`
	ID      string    `json:"id"`
	Created time.Time `json:"created,omitempty"`
}

// String implements the Stringer interface for Name
func (n *Name) String() string {
	return n.First + " " + n.Last
}

// Gateway represents a gateway on the account server
type Gateway struct {
	ID               string           `json:"id" valid:"required"`
	EUI              types.GatewayEUI `json:"eui" valid:"required"`
	Activated        bool             `json:"activated"`
	FrequencyPlan    string           `json:"frequency_plan"`
	FrequencyPlanURL string           `json:"frequency_plan_url"`
	LocationPublic   bool             `json:"location_public"`
	StatusPublic     bool             `json:"status_public"`
	Location         *Location        `json:"location"`
	Collaborators    []Collaborator   `json:"collaborator"`
	Key              string           `json:"key"`
}

type Location struct {
	Longitude float64 `json:"lng"`
	Latitude  float64 `json:"lat"`
}
<|endoftext|>


<commit_before>package worker

import (
	"fmt"
	"log"
	"os"
	"time"
)

type Logger struct {
	*log.Logger
}

func NewLogger() *Logger {
	return &Logger{log.New(os.Stdout, "[worker] ", 0)}
}

func (l *Logger) Exec(sw StatusWriter, fact string, args *Args, next JobRunner) {
	jinfo := fact + " " + args.String()
	start := time.Now()

	l.Println(jinfo, "... started")

	next(sw, fact, args)

	status := "OK"
	if !sw.OK() {
		status = fmt.Sprintf("FAIL (%v)", sw.Get())
	}

	l.Printf("%s ... in %v ... %v", jinfo, time.Since(start), status)
}
<commit_msg>Update started/completed messages<commit_after>package worker

import (
	"fmt"
	"log"
	"os"
	"time"
)

type Logger struct {
	*log.Logger
}

func NewLogger() *Logger {
	return &Logger{log.New(os.Stdout, "[worker] ", 0)}
}

func (l *Logger) Exec(sw StatusWriter, fact string, args *Args, next JobRunner) {
	jinfo := fact + " " + args.String()
	start := time.Now()

	l.Println(jinfo, "started ...")

	next(sw, fact, args)

	status := "OK"
	if !sw.OK() {
		status = fmt.Sprintf("FAIL (%v)", sw.Get())
	}

	l.Printf("%s completed in %v ... %v", jinfo, time.Since(start), status)
}
<|endoftext|>


<commit_before>package aci

import (
	"archive/tar"
	"io"
	"os"
	"path/filepath"

	"github.com/appc/spec/pkg/tarheader"
)

// BuildWalker creates a filepath.WalkFunc that walks over
// the given root and adds files to the given
// ArchiveWriter
func BuildWalker(root string, aw ArchiveWriter) filepath.WalkFunc {
	// cache of inode -> filepath, used to leverage hard links in the archive
	inos := map[uint64]string{}
	return func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		relpath, err := filepath.Rel(root, path)
		if err != nil {
			return err
		}
		if relpath == "." {
			return nil
		}
		if relpath == ManifestFile {
			// ignore; this will be written by the archive writer
			// TODO(jonboulle): does this make sense? maybe just remove from archivewriter?
			return nil
		}

		link := ""
		var r io.Reader
		switch info.Mode() & os.ModeType {
		case os.ModeCharDevice:
		case os.ModeDevice:
		case os.ModeDir:
		case os.ModeSymlink:
			target, err := os.Readlink(path)
			if err != nil {
				return err
			}
			link = target
		default:
			file, err := os.Open(path)
			if err != nil {
				return err
			}
			defer file.Close()
			r = file
		}

		hdr, err := tar.FileInfoHeader(info, link)
		if err != nil {
			panic(err)
		}
		// Because os.FileInfo's Name method returns only the base
		// name of the file it describes, it may be necessary to
		// modify the Name field of the returned header to provide the
		// full path name of the file.
		hdr.Name = relpath
		tarheader.Populate(hdr, info, inos)
		// If the file is a hard link to a file we've already seen, we
		// don't need the contents
		if hdr.Typeflag == tar.TypeLink {
			hdr.Size = 0
			r = nil
		}
		if err := aw.AddFile(hdr, r); err != nil {
			return err
		}

		return nil
	}
}
<commit_msg>aci/build: update docstring<commit_after>package aci

import (
	"archive/tar"
	"io"
	"os"
	"path/filepath"

	"github.com/appc/spec/pkg/tarheader"
)

// BuildWalker creates a filepath.WalkFunc that walks over the given root
// (which should represent an ACI layout on disk) and adds the files in the
// rootfs/ subdirectory to the given ArchiveWriter
func BuildWalker(root string, aw ArchiveWriter) filepath.WalkFunc {
	// cache of inode -> filepath, used to leverage hard links in the archive
	inos := map[uint64]string{}
	return func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		relpath, err := filepath.Rel(root, path)
		if err != nil {
			return err
		}
		if relpath == "." {
			return nil
		}
		if relpath == ManifestFile {
			// ignore; this will be written by the archive writer
			// TODO(jonboulle): does this make sense? maybe just remove from archivewriter?
			return nil
		}

		link := ""
		var r io.Reader
		switch info.Mode() & os.ModeType {
		case os.ModeCharDevice:
		case os.ModeDevice:
		case os.ModeDir:
		case os.ModeSymlink:
			target, err := os.Readlink(path)
			if err != nil {
				return err
			}
			link = target
		default:
			file, err := os.Open(path)
			if err != nil {
				return err
			}
			defer file.Close()
			r = file
		}

		hdr, err := tar.FileInfoHeader(info, link)
		if err != nil {
			panic(err)
		}
		// Because os.FileInfo's Name method returns only the base
		// name of the file it describes, it may be necessary to
		// modify the Name field of the returned header to provide the
		// full path name of the file.
		hdr.Name = relpath
		tarheader.Populate(hdr, info, inos)
		// If the file is a hard link to a file we've already seen, we
		// don't need the contents
		if hdr.Typeflag == tar.TypeLink {
			hdr.Size = 0
			r = nil
		}
		if err := aw.AddFile(hdr, r); err != nil {
			return err
		}

		return nil
	}
}
<|endoftext|>


<commit_before>package prom2cloudwatch

import (
	"context"
	"errors"
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/common/expfmt"
	"github.com/prometheus/common/model"
)

const (
	batchSize = 100

	cwHighResLabel = "__cw_high_res"
	cwUnitLabel    = "__cw_unit"
)

// Config defines configuration options for Bridge
type Config struct {
	// Required. The Prometheus namespace/prefix to scrape. Each Bridge only supports 1 prefix.
	// If multiple prefixes are required, multiple Bridges must be used.
	PrometheusNamespace string

	// Required. The CloudWatch namespace under which metrics should be published
	CloudWatchNamespace string

	// Required. The AWS Region to use
	CloudWatchRegion string

	// The frequency with which metrics should be published to Cloudwatch. Default: 15s
	Interval time.Duration

	// Timeout for sending metrics to Cloudwatch. Default: 1s
	Timeout time.Duration

	// Custom HTTP Client to use with the Cloudwatch API. Default: http.Client{}
	// If Config.Timeout is supplied, it will override any timeout defined on
	// the supplied http.Client
	Client *http.Client

	// Logger that messages are written to. Default: nil
	Logger Logger

	// The Gatherer to use for metrics. Default: prometheus.DefaultGatherer
	Gatherer prometheus.Gatherer

	// Only publish whitelisted metrics
	WhitelistOnly bool

	// List of metrics that should be published, causing all others to be ignored.
	// Config.WhitelistOnly must be set to true for this to take effect.
	Whitelist []string

	// List of metrics that should never be published. This setting overrides entries in Config.Whitelist
	Blacklist []string
}

// Bridge pushes metrics to AWS Cloudwatch
type Bridge struct {
	interval time.Duration
	timeout  time.Duration

	promNamespace string
	cwNamespace   string

	useWhitelist bool
	whitelist    map[string]struct{}
	blacklist    map[string]struct{}

	logger Logger
	g      prometheus.Gatherer
	cw     *cloudwatch.CloudWatch
}

// NewBridge initializes and returns a pointer to a Bridge using the
// supplied configuration, or an error if there is a problem with
// the configuration
func NewBridge(c *Config) (*Bridge, error) {
	b := &Bridge{}

	if c.PrometheusNamespace == "" {
		return nil, errors.New("PrometheusNamespace must not be empty")
	}
	b.promNamespace = c.PrometheusNamespace

	if c.CloudWatchNamespace == "" {
		return nil, errors.New("CloudWatchNamespace must not be empty")
	}
	b.cwNamespace = c.CloudWatchNamespace

	if c.Interval > 0 {
		b.interval = c.Interval
	} else {
		b.interval = 15 * time.Second
	}

	var client *http.Client
	if c.Client != nil {
		client = c.Client
	} else {
		client = &http.Client{}
	}

	if c.Timeout > 0 {
		client.Timeout = c.Timeout
	} else {
		client.Timeout = time.Second
	}

	if c.Logger != nil {
		b.logger = c.Logger
	}

	if c.Gatherer != nil {
		b.g = c.Gatherer
	} else {
		b.g = prometheus.DefaultGatherer
	}

	b.useWhitelist = c.WhitelistOnly
	b.whitelist = make(map[string]struct{}, len(c.Whitelist))
	for _, v := range c.Whitelist {
		b.whitelist[v] = struct{}{}
	}

	b.blacklist = make(map[string]struct{}, len(c.Blacklist))
	for _, v := range c.Blacklist {
		b.blacklist[v] = struct{}{}
	}

	// Use default credential provider, which I believe supports the standard
	// AWS_* environment variables, and the shared credential file under ~/.aws
	sess, err := session.NewSession(aws.NewConfig().WithHTTPClient(client).WithRegion(c.CloudWatchRegion))
	if err != nil {
		return nil, err
	}

	b.cw = cloudwatch.New(sess)
	return b, nil
}

// Logger is the minimal interface Bridge needs for logging. Note that
// log.Logger from the standard library implements this interface, and it is
// easy to implement by custom loggers, if they don't do so already anyway.
// Taken from https://github.com/prometheus/client_golang/blob/master/prometheus/graphite/bridge.go
type Logger interface {
	Println(v ...interface{})
}

// Run starts a loop that will push metrics to Cloudwatch at the
// configured interval. Run accepts a context.Context to support
// cancellation.
func (b *Bridge) Run(ctx context.Context) {
	ticker := time.NewTicker(b.interval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			if err := b.Publish(); err != nil && b.logger != nil {
				b.logger.Println("error publishing to Cloudwatch:", err)
			}
		case <-ctx.Done():
			if b.logger != nil {
				b.logger.Println("stopping Cloudwatch publisher")
			}
			return
		}
	}
}

// Publish publishes the Prometheus metrics to Cloudwatch
func (b *Bridge) Publish() error {
	mfs, err := b.g.Gather()
	if err != nil {
		return err
	}

	return b.publishMetrics(mfs)
}

// NOTE: The CloudWatch API has the following limitations:
//		- Max 40kb request size
//		- Single namespace per request
//		- Max 10 dimensions per metric
func (b *Bridge) publishMetrics(mfs []*dto.MetricFamily) error {
	vec, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{
		Timestamp: model.Now(),
	}, mfs...)
	if err != nil {
		return err
	}

	data := make([]*cloudwatch.MetricDatum, 0, batchSize)
	for _, s := range vec {
		name := getName(s.Metric)
		if b.isWhitelisted(name) {
			data = appendDatum(data, name, s)
		}

		// punt on the 40KB size limitation. Will see how this works out in practice
		if len(data) == batchSize {
			if err := b.flush(data); err != nil {
				b.logger.Println("error publishing to Cloudwatch:", err)
			}
			data = make([]*cloudwatch.MetricDatum, 0, batchSize)
		}
	}

	return b.flush(data)
}

func (b *Bridge) flush(data []*cloudwatch.MetricDatum) error {
	if len(data) > 0 {
		in := &cloudwatch.PutMetricDataInput{
			MetricData: data,
			Namespace:  &b.cwNamespace,
		}
		_, err := b.cw.PutMetricData(in)
		return err
	}
	return nil
}

func (b *Bridge) isWhitelisted(name string) bool {
	if !strings.HasPrefix(name, b.promNamespace) {
		return false
	} else if _, ok := b.blacklist[name]; ok {
		return false
	}

	if b.useWhitelist {
		if name == "" {
			return false
		}
		_, ok := b.whitelist[name]
		return ok
	}
	return true
}

func appendDatum(data []*cloudwatch.MetricDatum, name string, s *model.Sample) []*cloudwatch.MetricDatum {
	d := &cloudwatch.MetricDatum{}
	d.SetMetricName(name).
		SetValue(float64(s.Value)).
		SetTimestamp(s.Timestamp.Time()).
		SetDimensions(getDimensions(s.Metric)).
		SetStorageResolution(getResolution(s.Metric)).
		SetUnit(getUnit(s.Metric))
	return append(data, d)
}

func getName(m model.Metric) string {
	if n, ok := m[model.MetricNameLabel]; ok {
		return string(n)
	}
	return ""
}

// getDimensions returns up to 10 dimensions for the provided metric - one for each label (except the __name__ label)
//
// If a metric has more than 10 labels, it attempts to behave deterministically by sorting the labels lexicographically,
// and returning the first 10 labels as dimensions
func getDimensions(m model.Metric) []*cloudwatch.Dimension {
	if len(m) == 0 {
		return make([]*cloudwatch.Dimension, 0)
	} else if _, ok := m[model.MetricNameLabel]; len(m) == 1 && ok {
		return make([]*cloudwatch.Dimension, 0)
	}
	names := make([]string, 0, len(m))
	for k := range m {
		if !(k == model.MetricNameLabel || k == cwHighResLabel || k == cwUnitLabel) {
			names = append(names, string(k))
		}
	}

	sort.Strings(names)
	if len(names) > 10 {
		names = names[:10]
	}
	dims := make([]*cloudwatch.Dimension, 0, len(names))
	for _, k := range names {
		dims = append(dims, new(cloudwatch.Dimension).SetName(k).SetValue(string(m[model.LabelName(k)])))
	}
	return dims
}

// Returns 1 if the metric contains a __cw_high_res label, otherwise it return 60
func getResolution(m model.Metric) int64 {
	if _, ok := m[cwHighResLabel]; ok {
		return 1
	}
	return 60
}

// TODO: can we infer the proper unit based on the metric name?
func getUnit(m model.Metric) string {
	if u, ok := m[cwUnitLabel]; ok {
		return string(u)
	}
	return ""
}
<commit_msg>set batch size to 20<commit_after>package prom2cloudwatch

import (
	"context"
	"errors"
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/common/expfmt"
	"github.com/prometheus/common/model"
)

const (
	batchSize = 20

	cwHighResLabel = "__cw_high_res"
	cwUnitLabel    = "__cw_unit"
)

// Config defines configuration options for Bridge
type Config struct {
	// Required. The Prometheus namespace/prefix to scrape. Each Bridge only supports 1 prefix.
	// If multiple prefixes are required, multiple Bridges must be used.
	PrometheusNamespace string

	// Required. The CloudWatch namespace under which metrics should be published
	CloudWatchNamespace string

	// Required. The AWS Region to use
	CloudWatchRegion string

	// The frequency with which metrics should be published to Cloudwatch. Default: 15s
	Interval time.Duration

	// Timeout for sending metrics to Cloudwatch. Default: 1s
	Timeout time.Duration

	// Custom HTTP Client to use with the Cloudwatch API. Default: http.Client{}
	// If Config.Timeout is supplied, it will override any timeout defined on
	// the supplied http.Client
	Client *http.Client

	// Logger that messages are written to. Default: nil
	Logger Logger

	// The Gatherer to use for metrics. Default: prometheus.DefaultGatherer
	Gatherer prometheus.Gatherer

	// Only publish whitelisted metrics
	WhitelistOnly bool

	// List of metrics that should be published, causing all others to be ignored.
	// Config.WhitelistOnly must be set to true for this to take effect.
	Whitelist []string

	// List of metrics that should never be published. This setting overrides entries in Config.Whitelist
	Blacklist []string
}

// Bridge pushes metrics to AWS Cloudwatch
type Bridge struct {
	interval time.Duration
	timeout  time.Duration

	promNamespace string
	cwNamespace   string

	useWhitelist bool
	whitelist    map[string]struct{}
	blacklist    map[string]struct{}

	logger Logger
	g      prometheus.Gatherer
	cw     *cloudwatch.CloudWatch
}

// NewBridge initializes and returns a pointer to a Bridge using the
// supplied configuration, or an error if there is a problem with
// the configuration
func NewBridge(c *Config) (*Bridge, error) {
	b := &Bridge{}

	if c.PrometheusNamespace == "" {
		return nil, errors.New("PrometheusNamespace must not be empty")
	}
	b.promNamespace = c.PrometheusNamespace

	if c.CloudWatchNamespace == "" {
		return nil, errors.New("CloudWatchNamespace must not be empty")
	}
	b.cwNamespace = c.CloudWatchNamespace

	if c.Interval > 0 {
		b.interval = c.Interval
	} else {
		b.interval = 15 * time.Second
	}

	var client *http.Client
	if c.Client != nil {
		client = c.Client
	} else {
		client = &http.Client{}
	}

	if c.Timeout > 0 {
		client.Timeout = c.Timeout
	} else {
		client.Timeout = time.Second
	}

	if c.Logger != nil {
		b.logger = c.Logger
	}

	if c.Gatherer != nil {
		b.g = c.Gatherer
	} else {
		b.g = prometheus.DefaultGatherer
	}

	b.useWhitelist = c.WhitelistOnly
	b.whitelist = make(map[string]struct{}, len(c.Whitelist))
	for _, v := range c.Whitelist {
		b.whitelist[v] = struct{}{}
	}

	b.blacklist = make(map[string]struct{}, len(c.Blacklist))
	for _, v := range c.Blacklist {
		b.blacklist[v] = struct{}{}
	}

	// Use default credential provider, which I believe supports the standard
	// AWS_* environment variables, and the shared credential file under ~/.aws
	sess, err := session.NewSession(aws.NewConfig().WithHTTPClient(client).WithRegion(c.CloudWatchRegion))
	if err != nil {
		return nil, err
	}

	b.cw = cloudwatch.New(sess)
	return b, nil
}

// Logger is the minimal interface Bridge needs for logging. Note that
// log.Logger from the standard library implements this interface, and it is
// easy to implement by custom loggers, if they don't do so already anyway.
// Taken from https://github.com/prometheus/client_golang/blob/master/prometheus/graphite/bridge.go
type Logger interface {
	Println(v ...interface{})
}

// Run starts a loop that will push metrics to Cloudwatch at the
// configured interval. Run accepts a context.Context to support
// cancellation.
func (b *Bridge) Run(ctx context.Context) {
	ticker := time.NewTicker(b.interval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			if err := b.Publish(); err != nil && b.logger != nil {
				b.logger.Println("error publishing to Cloudwatch:", err)
			}
		case <-ctx.Done():
			if b.logger != nil {
				b.logger.Println("stopping Cloudwatch publisher")
			}
			return
		}
	}
}

// Publish publishes the Prometheus metrics to Cloudwatch
func (b *Bridge) Publish() error {
	mfs, err := b.g.Gather()
	if err != nil {
		return err
	}

	return b.publishMetrics(mfs)
}

// NOTE: The CloudWatch API has the following limitations:
//		- Max 40kb request size
//		- Single namespace per request
//		- Max 10 dimensions per metric
func (b *Bridge) publishMetrics(mfs []*dto.MetricFamily) error {
	vec, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{
		Timestamp: model.Now(),
	}, mfs...)
	if err != nil {
		return err
	}

	data := make([]*cloudwatch.MetricDatum, 0, batchSize)
	for _, s := range vec {
		name := getName(s.Metric)
		if b.isWhitelisted(name) {
			data = appendDatum(data, name, s)
		}

		// punt on the 40KB size limitation. Will see how this works out in practice
		if len(data) == batchSize {
			if err := b.flush(data); err != nil {
				b.logger.Println("error publishing to Cloudwatch:", err)
			}
			data = make([]*cloudwatch.MetricDatum, 0, batchSize)
		}
	}

	return b.flush(data)
}

func (b *Bridge) flush(data []*cloudwatch.MetricDatum) error {
	if len(data) > 0 {
		in := &cloudwatch.PutMetricDataInput{
			MetricData: data,
			Namespace:  &b.cwNamespace,
		}
		_, err := b.cw.PutMetricData(in)
		return err
	}
	return nil
}

func (b *Bridge) isWhitelisted(name string) bool {
	if !strings.HasPrefix(name, b.promNamespace) {
		return false
	} else if _, ok := b.blacklist[name]; ok {
		return false
	}

	if b.useWhitelist {
		if name == "" {
			return false
		}
		_, ok := b.whitelist[name]
		return ok
	}
	return true
}

func appendDatum(data []*cloudwatch.MetricDatum, name string, s *model.Sample) []*cloudwatch.MetricDatum {
	d := &cloudwatch.MetricDatum{}
	d.SetMetricName(name).
		SetValue(float64(s.Value)).
		SetTimestamp(s.Timestamp.Time()).
		SetDimensions(getDimensions(s.Metric)).
		SetStorageResolution(getResolution(s.Metric)).
		SetUnit(getUnit(s.Metric))
	return append(data, d)
}

func getName(m model.Metric) string {
	if n, ok := m[model.MetricNameLabel]; ok {
		return string(n)
	}
	return ""
}

// getDimensions returns up to 10 dimensions for the provided metric - one for each label (except the __name__ label)
//
// If a metric has more than 10 labels, it attempts to behave deterministically by sorting the labels lexicographically,
// and returning the first 10 labels as dimensions
func getDimensions(m model.Metric) []*cloudwatch.Dimension {
	if len(m) == 0 {
		return make([]*cloudwatch.Dimension, 0)
	} else if _, ok := m[model.MetricNameLabel]; len(m) == 1 && ok {
		return make([]*cloudwatch.Dimension, 0)
	}
	names := make([]string, 0, len(m))
	for k := range m {
		if !(k == model.MetricNameLabel || k == cwHighResLabel || k == cwUnitLabel) {
			names = append(names, string(k))
		}
	}

	sort.Strings(names)
	if len(names) > 10 {
		names = names[:10]
	}
	dims := make([]*cloudwatch.Dimension, 0, len(names))
	for _, k := range names {
		dims = append(dims, new(cloudwatch.Dimension).SetName(k).SetValue(string(m[model.LabelName(k)])))
	}
	return dims
}

// Returns 1 if the metric contains a __cw_high_res label, otherwise it return 60
func getResolution(m model.Metric) int64 {
	if _, ok := m[cwHighResLabel]; ok {
		return 1
	}
	return 60
}

// TODO: can we infer the proper unit based on the metric name?
func getUnit(m model.Metric) string {
	if u, ok := m[cwUnitLabel]; ok {
		return string(u)
	}
	return ""
}
<|endoftext|>