├── .gitignore ├── main.go ├── Dockerfile ├── pkg ├── ratelimit │ └── transport.go ├── client │ ├── service.go │ ├── services │ │ ├── factories.go │ │ ├── kafka.go │ │ ├── elasticsearch.go │ │ ├── dds.go │ │ ├── ecs.go │ │ ├── clickhouse.go │ │ ├── polardb.go │ │ ├── rds.go │ │ ├── r-kvstore.go │ │ └── slb.go │ └── metric.go ├── config │ ├── config.go │ └── metric.go └── collector │ ├── instanceinfo.go │ └── collector.go ├── Makefile ├── .github └── workflows │ └── docker-image.yml ├── README.md ├── go.mod ├── cmd ├── cmd.go └── serve.go ├── deploy └── rules.yaml ├── go.sum └── example.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Kubernetes Generated files - skip generated files, except for vendored files 17 | 18 | !vendor/**/zz_generated.* 19 | 20 | # editor and IDE paraphernalia 21 | .idea 22 | *.swp 23 | *.swo 24 | *~ 25 | 26 | build/_output 27 | vendor/ 28 | .vscode/% 29 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/alecthomas/kingpin/v2" 5 | "github.com/prometheus/client_golang/prometheus" 6 | versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version" 7 | "github.com/prometheus/common/version" 8 | 9 | "github.com/fengxsong/aliyun_exporter/cmd" 10 | "github.com/fengxsong/aliyun_exporter/pkg/collector" 11 | ) 12 | 13 | func init() { 14 | prometheus.MustRegister(versioncollector.NewCollector(collector.Name())) 15 | } 16 | 17 | func main() { 18 | kingpin.Version(version.Print(collector.Name())) 19 | kingpin.HelpFlag.Short('h') 20 | cmd.AddCommands(kingpin.CommandLine) 21 | kingpin.Parse() 22 | } 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine as builder 2 | ARG LDFLAGS 3 | 4 | RUN apk --no-cache add ca-certificates \ 5 | && rm -Rf /var/cache/apk/* 6 | 7 | WORKDIR /workspace 8 | COPY go.mod go.sum /workspace/ 9 | RUN go mod download 10 | 11 | COPY cmd /workspace/cmd 12 | COPY pkg /workspace/pkg 13 | COPY main.go /workspace/ 14 | RUN CGO_ENABLED=0 go build -a -ldflags "${LDFLAGS}" -o aliyun_exporter main.go && ./aliyun_exporter --version 15 | 16 | FROM alpine:3 17 | 18 | RUN addgroup -S appusers && adduser -S appuser -G appusers 19 | USER appuser 20 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 21 | COPY --from=builder /workspace/aliyun_exporter /usr/local/bin/aliyun_exporter 22 | 23 | ENTRYPOINT ["/usr/local/bin/aliyun_exporter"] 24 | -------------------------------------------------------------------------------- /pkg/ratelimit/transport.go: -------------------------------------------------------------------------------- 1 | package ratelimit 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go.uber.org/ratelimit" 7 | ) 8 | 9 | // Transport implements http.RoundTripper. 10 | type Transport struct { 11 | Transport http.RoundTripper // Used to make actual requests. 12 | Limiter ratelimit.Limiter 13 | } 14 | 15 | // RoundTrip ensures requests are performed within the rate limiting constraints. 16 | func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { 17 | _ = t.Limiter.Take() 18 | if t.Transport == nil { 19 | t.Transport = http.DefaultTransport 20 | } 21 | return t.Transport.RoundTrip(r) 22 | } 23 | 24 | // NewWithLimiter returns a RoundTripper capable of rate limiting http requests. 25 | func NewWithLimiter(limiter ratelimit.Limiter) http.RoundTripper { 26 | return &Transport{Transport: http.DefaultTransport, Limiter: limiter} 27 | } 28 | 29 | // New .. 30 | func New(limit int) http.RoundTripper { 31 | return NewWithLimiter(ratelimit.New(limit)) 32 | } 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHORT_NAME ?= aliyun_exporter 2 | 3 | BUILD_DATE = $(shell date -u +'%Y-%m-%dT%H:%M:%SZ') 4 | HASH = $(shell git describe --dirty --tags --always) 5 | REF_NAME = $(shell git rev-parse --abbrev-ref HEAD) 6 | BUILDUSER = $(shell whoami) 7 | VERSION ?= unknown 8 | REPO = github.com/fengxsong/aliyun_exporter 9 | 10 | BUILD_PATH = main.go 11 | OUTPUT_PATH = build/_output/bin/$(SHORT_NAME) 12 | 13 | LDFLAGS := -s -X github.com/prometheus/common/version.Version=${VERSION} \ 14 | -X github.com/prometheus/common/version.Revision=${HASH} \ 15 | -X github.com/prometheus/common/version.Branch=${REF_NAME} \ 16 | -X github.com/prometheus/common/version.BuildUser=${BUILDUSER} \ 17 | -X github.com/prometheus/common/version.BuildDate=${BUILD_DATE} 18 | 19 | deps: 20 | go mod tidy 21 | 22 | bin: 23 | CGO_ENABLED=0 go build -a -installsuffix cgo -ldflags "${LDFLAGS}" -o ${OUTPUT_PATH} ${BUILD_PATH} || exit 1 24 | 25 | linux-bin: 26 | CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags "${LDFLAGS}" -o ${OUTPUT_PATH} ${BUILD_PATH} || exit 1 27 | 28 | upx: 29 | upx ${OUTPUT_PATH} 30 | -------------------------------------------------------------------------------- /pkg/client/service.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/go-kit/log" 7 | "github.com/prometheus/client_golang/prometheus" 8 | 9 | "github.com/fengxsong/aliyun_exporter/pkg/client/services" 10 | ) 11 | 12 | // ServiceClient ... 13 | type ServiceClient struct { 14 | collectors map[string]services.Collector 15 | } 16 | 17 | // Collect do actural collection 18 | func (c *ServiceClient) Collect(namespace string, sub string, region string, ch chan<- prometheus.Metric) error { 19 | key := sub + region 20 | collector, ok := c.collectors[key] 21 | if !ok { 22 | return nil 23 | } 24 | return collector.Collect(namespace, ch) 25 | } 26 | 27 | // NewServiceClient create service client 28 | func NewServiceClient(ak, secret string, regions []string, rt http.RoundTripper, logger log.Logger) (*ServiceClient, error) { 29 | sc := &ServiceClient{ 30 | collectors: make(map[string]services.Collector), 31 | } 32 | if logger == nil { 33 | logger = log.NewNopLogger() 34 | } 35 | for name, fn := range services.All() { 36 | for i := range regions { 37 | collector, err := fn(regions[i], ak, secret, logger) 38 | if err != nil { 39 | return nil, err 40 | } 41 | if client, ok := collector.(interface { 42 | SetTransport(http.RoundTripper) 43 | }); ok { 44 | client.SetTransport(rt) 45 | } 46 | sc.collectors[name+regions[i]] = collector 47 | } 48 | } 49 | return sc, nil 50 | } 51 | -------------------------------------------------------------------------------- /pkg/client/services/factories.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "sync" 7 | 8 | "github.com/go-kit/log" 9 | "github.com/prometheus/client_golang/prometheus" 10 | ) 11 | 12 | type Collector interface { 13 | Collect(string, chan<- prometheus.Metric) error 14 | } 15 | 16 | type NewCollectorFunc func(string, string, string, log.Logger) (Collector, error) 17 | 18 | // Names return all registered service collector name 19 | func Names() []string { 20 | names := make([]string, 0, len(factories)) 21 | for k := range factories { 22 | names = append(names, k) 23 | } 24 | sort.Strings(names) 25 | return names 26 | } 27 | 28 | // All return all registered newCollector functions map 29 | func All() map[string]NewCollectorFunc { 30 | return factories 31 | } 32 | 33 | var lock = &sync.Mutex{} 34 | var factories = map[string]NewCollectorFunc{} 35 | 36 | // Register register new collector function 37 | func register(name string, fn NewCollectorFunc) { 38 | lock.Lock() 39 | defer lock.Unlock() 40 | if _, ok := factories[name]; ok { 41 | panic("named collector function already registered") 42 | } 43 | factories[name] = fn 44 | } 45 | 46 | // the maximum page size is 100, otherwise it'll return InvalidParameter error 47 | const pageSize = 100 48 | 49 | func newDescOfInstnaceInfo(namespace string, sub string, variableLabels []string) *prometheus.Desc { 50 | return prometheus.NewDesc( 51 | prometheus.BuildFQName(namespace, sub, "instance_info"), 52 | fmt.Sprintf("Information of instance %s", sub), 53 | variableLabels, nil, 54 | ) 55 | } 56 | 57 | type result struct { 58 | v any 59 | err error 60 | } 61 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Build and Push Docker Images 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - test 8 | tags: ["v*.*.*"] 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | build-and-push: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | 21 | - name: Set up QEMU 22 | uses: docker/setup-qemu-action@v3 23 | 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v3 26 | 27 | - name: Login to ghcr.io 28 | uses: docker/login-action@v3 29 | with: 30 | registry: ghcr.io 31 | username: ${{ github.repository_owner }} 32 | password: ${{ secrets.GITHUB_TOKEN }} 33 | 34 | - name: Get current date 35 | id: date 36 | run: echo "::set-output name=date::$(date +'%Y-%m-%d')" 37 | 38 | - name: Build and push Docker image 39 | id: docker_build 40 | uses: docker/build-push-action@v6 41 | with: 42 | context: . 43 | file: ./Dockerfile 44 | push: true 45 | platforms: linux/amd64,linux/arm64 46 | build-args: | 47 | LDFLAGS=-s -X github.com/prometheus/common/version.Version=${{ github.ref_name }} -X github.com/prometheus/common/version.Revision=${{ github.sha }} -X github.com/prometheus/common/version.Branch=${{ github.ref_name }} -X github.com/prometheus/common/version.BuildUser=${{ github.triggering_actor }} -X github.com/prometheus/common/version.BuildDate=${{ steps.date.outputs.date }} 48 | tags: | 49 | ghcr.io/${{ github.repository }}:${{ github.ref_type == 'tag' && github.ref_name || github.sha }} 50 | ghcr.io/${{ github.repository }}:latest 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aliyun Exporter 2 | 3 | a prometheus exporter for Aliyun CloudMonitor service. Written in Golang, inspired by [aliyun_exporter](https://github.com/aylei/aliyun_exporter). 4 | 5 | ## Develop 6 | 7 | ```bash 8 | git clone https://github.com/fengxsong/aliyun_exporter 9 | cd aliyun_exporter 10 | make tidy 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```bash 16 | make bin 17 | # generate example of config 18 | ./build/_output/bin/aliyun_exporter generate-example-config --accesskey xxxx ----accesskeysecret xxxx 19 | # run http metrics handler 20 | ./build/_output/bin/aliyun_exporter serve [--config=/path/of/config] 21 | ``` 22 | 23 | ### Create a prometheus scrape job 24 | 25 | ```yaml 26 | - job_name: 'aliyun_exporter' 27 | scrape_interval: 60s 28 | scrape_timeout: 60s 29 | static_configs: 30 | - targets: ['aliyun_exporter:9527'] 31 | labels: 32 | account_name: xxxx 33 | provider: aliyun # or aliyun_jst 34 | ``` 35 | 36 | ### Prometheus rules sample 37 | 38 | [sample file](https://../deploy/rules.yaml) 39 | 40 | ## Limitation 41 | 42 | - an exporter instance can only scrape metrics from single region 43 | 44 | ## TODO 45 | 46 | - grafana dashboard 47 | 48 | ## Ref 49 | 50 | - [Metrics listing](https://www.alibabacloud.com/help/en/cms/support/appendix-1-metrics) 51 | - [DescribeMetricMetaList](https://www.alibabacloud.com/help/en/cms/developer-reference/api-cms-2019-01-01-describemetricmetalist?spm=a2c63.p38356.0.0.12e23344MoHCLk) 52 | - [DescribeMetricLast](https://www.alibabacloud.com/help/en/cms/developer-reference/api-cms-2019-01-01-describemetriclast?spm=a2c63.p38356.0.0.7e7d5b35Ml1iqb) 53 | - [IAM access](https://help.aliyun.com/zh/ram/developer-reference/aliyuncloudmonitorreadonlyaccess?spm=a2c4g.11186623.0.0.4eafd1eaFYcOoS) 54 | - [SDK](https://github.com/aliyun/alibaba-cloud-sdk-go) 55 | -------------------------------------------------------------------------------- /pkg/client/services/kafka.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | kafka "github.com/aliyun/alibaba-cloud-sdk-go/services/alikafka" 5 | "github.com/go-kit/log" 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | type kafkaClient struct { 10 | *kafka.Client 11 | desc *prometheus.Desc 12 | logger log.Logger 13 | } 14 | 15 | func (c *kafkaClient) Collect(namespace string, ch chan<- prometheus.Metric) error { 16 | if c.desc == nil { 17 | c.desc = newDescOfInstnaceInfo(namespace, "acs_kafka", []string{"regionId", "instanceId", "name", "endpoint", "type"}) 18 | } 19 | 20 | req := kafka.CreateGetInstanceListRequest() 21 | resultChan := make(chan *result, 1<<10) 22 | 23 | go func() { 24 | defer close(resultChan) 25 | for hasNextPage, pageNum := true, 1; hasNextPage != false; pageNum++ { 26 | response, err := c.GetInstanceList(req) 27 | if err != nil { 28 | resultChan <- &result{err: err} 29 | return 30 | } 31 | if len(response.InstanceList.InstanceVO) < pageSize && len(response.InstanceList.InstanceVO) == 0 { 32 | hasNextPage = false 33 | } 34 | for i := range response.InstanceList.InstanceVO { 35 | resultChan <- &result{v: response.InstanceList.InstanceVO[i]} 36 | } 37 | } 38 | }() 39 | for res := range resultChan { 40 | if res.err != nil { 41 | return res.err 42 | } 43 | ins := res.v.(kafka.InstanceVO) 44 | ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, 1.0, 45 | ins.RegionId, ins.InstanceId, ins.Name, ins.DomainEndpoint, ins.SpecType) 46 | } 47 | return nil 48 | } 49 | 50 | func init() { 51 | register("acs_kafka", func(s1, s2, s3 string, l log.Logger) (Collector, error) { 52 | client, err := kafka.NewClientWithAccessKey(s1, s2, s3) 53 | if err != nil { 54 | return nil, err 55 | } 56 | return &kafkaClient{Client: client, logger: l}, nil 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fengxsong/aliyun_exporter 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/alecthomas/kingpin/v2 v2.4.0 7 | github.com/aliyun/alibaba-cloud-sdk-go v1.62.807 8 | github.com/go-kit/log v0.2.1 9 | github.com/prometheus/client_golang v1.19.1 10 | github.com/prometheus/common v0.55.0 11 | github.com/prometheus/exporter-toolkit v0.11.0 12 | go.uber.org/ratelimit v0.3.1 13 | gopkg.in/yaml.v3 v3.0.1 14 | ) 15 | 16 | require ( 17 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect 18 | github.com/benbjohnson/clock v1.3.5 // indirect 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 21 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 22 | github.com/go-logfmt/logfmt v0.6.0 // indirect 23 | github.com/jmespath/go-jmespath v0.4.0 // indirect 24 | github.com/jpillora/backoff v1.0.0 // indirect 25 | github.com/json-iterator/go v1.1.12 // indirect 26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 27 | github.com/modern-go/reflect2 v1.0.2 // indirect 28 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 29 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect 30 | github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect 31 | github.com/prometheus/client_model v0.6.1 // indirect 32 | github.com/prometheus/procfs v0.15.1 // indirect 33 | github.com/xhit/go-str2duration/v2 v2.1.0 // indirect 34 | golang.org/x/crypto v0.24.0 // indirect 35 | golang.org/x/net v0.26.0 // indirect 36 | golang.org/x/oauth2 v0.21.0 // indirect 37 | golang.org/x/sync v0.7.0 // indirect 38 | golang.org/x/sys v0.23.0 // indirect 39 | golang.org/x/text v0.16.0 // indirect 40 | google.golang.org/protobuf v1.34.2 // indirect 41 | gopkg.in/ini.v1 v1.67.0 // indirect 42 | gopkg.in/yaml.v2 v2.4.0 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /pkg/client/services/elasticsearch.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 5 | "github.com/aliyun/alibaba-cloud-sdk-go/services/elasticsearch" 6 | "github.com/go-kit/log" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | type esClient struct { 11 | *elasticsearch.Client 12 | desc *prometheus.Desc 13 | logger log.Logger 14 | } 15 | 16 | func (c *esClient) Collect(namespace string, ch chan<- prometheus.Metric) error { 17 | if c.desc == nil { 18 | c.desc = newDescOfInstnaceInfo(namespace, "acs_elasticsearch", []string{"instanceId", "desc", "status"}) 19 | } 20 | 21 | req := elasticsearch.CreateListInstanceRequest() 22 | req.Size = requests.NewInteger(pageSize) 23 | resultChan := make(chan *result, 1<<10) 24 | 25 | go func() { 26 | defer close(resultChan) 27 | for hasNextPage, pageNum := true, 1; hasNextPage != false; pageNum++ { 28 | req.Page = requests.NewInteger(pageNum) 29 | response, err := c.ListInstance(req) 30 | if err != nil { 31 | resultChan <- &result{err: err} 32 | return 33 | } 34 | if len(response.Result) < pageSize && len(response.Result) == 0 { 35 | hasNextPage = false 36 | } 37 | for i := range response.Result { 38 | resultChan <- &result{v: response.Result[i]} 39 | } 40 | } 41 | }() 42 | 43 | for res := range resultChan { 44 | if res.err != nil { 45 | return res.err 46 | } 47 | ins := res.v.(elasticsearch.Instance) 48 | ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, 1.0, 49 | ins.InstanceId, ins.Description, ins.Status) 50 | } 51 | return nil 52 | } 53 | 54 | func init() { 55 | register("acs_elasticsearch", func(s1, s2, s3 string, l log.Logger) (Collector, error) { 56 | client, err := elasticsearch.NewClientWithAccessKey(s1, s2, s3) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return &esClient{Client: client, logger: l}, nil 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /pkg/client/services/dds.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 5 | "github.com/aliyun/alibaba-cloud-sdk-go/services/dds" 6 | "github.com/go-kit/log" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | type ddsClient struct { 11 | *dds.Client 12 | desc *prometheus.Desc 13 | logger log.Logger 14 | } 15 | 16 | func (c *ddsClient) Collect(namespace string, ch chan<- prometheus.Metric) error { 17 | if c.desc == nil { 18 | c.desc = newDescOfInstnaceInfo(namespace, "acs_mongodb", []string{"regionId", "instanceId", "dbType", "desc", "status"}) 19 | } 20 | 21 | req := dds.CreateDescribeDBInstancesRequest() 22 | req.PageSize = requests.NewInteger(pageSize) 23 | resultChan := make(chan *result, 1<<10) 24 | var totalCount int 25 | 26 | go func() { 27 | defer close(resultChan) 28 | for hasNextPage, pageNum := true, 1; hasNextPage != false; pageNum++ { 29 | req.PageNumber = requests.NewInteger(pageNum) 30 | response, err := c.DescribeDBInstances(req) 31 | if err != nil { 32 | resultChan <- &result{err: err} 33 | return 34 | } 35 | totalCount += len(response.DBInstances.DBInstance) 36 | if totalCount >= response.TotalCount { 37 | hasNextPage = false 38 | } 39 | for i := range response.DBInstances.DBInstance { 40 | resultChan <- &result{v: response.DBInstances.DBInstance[i]} 41 | } 42 | } 43 | }() 44 | for res := range resultChan { 45 | if res.err != nil { 46 | return res.err 47 | } 48 | ins := res.v.(dds.DBInstance) 49 | ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, 1.0, 50 | ins.RegionId, ins.DBInstanceId, ins.DBInstanceType, ins.DBInstanceDescription, ins.DBInstanceStatus) 51 | } 52 | return nil 53 | } 54 | 55 | func init() { 56 | register("acs_mongodb", func(s1, s2, s3 string, l log.Logger) (Collector, error) { 57 | client, err := dds.NewClientWithAccessKey(s1, s2, s3) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return &ddsClient{Client: client, logger: l}, nil 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | 7 | "gopkg.in/yaml.v3" 8 | ) 9 | 10 | // Config exporter config 11 | type Config struct { 12 | AccessKey string `yaml:"access_key"` 13 | AccessKeySecret string `yaml:"access_key_secret"` 14 | Region string `yaml:"region"` 15 | Labels map[string]string `yaml:"labels,omitempty"` // todo: add extra labels 16 | Metrics map[string][]*Metric `yaml:"metrics"` // mapping for namespace and metrics 17 | InstanceInfo *InstanceInfo `yaml:"instance_info"` // enable scraping instance infos for label join 18 | } 19 | 20 | type InstanceInfo struct { 21 | Types []string `yaml:"types"` 22 | Regions []string `yaml:"regions"` 23 | } 24 | 25 | func (c *Config) validateAndSetDefaults() error { 26 | if c.Region == "" { 27 | c.Region = "cn-hangzhou" 28 | } 29 | ak := getEnvOrDefault("ALIBABA_CLOUD_ACCESS_KEY", c.AccessKey) 30 | secret := getEnvOrDefault("ALIBABA_CLOUD_ACCESS_KEY_SECRET", c.AccessKeySecret) 31 | if len(ak) == 0 || len(secret) == 0 { 32 | return errors.New("credentials not provide") 33 | } 34 | c.AccessKey = ak 35 | c.AccessKeySecret = secret 36 | if c.InstanceInfo != nil && len(c.InstanceInfo.Regions) == 0 { 37 | c.InstanceInfo.Regions = []string{c.Region} 38 | } 39 | for _, metrics := range c.Metrics { 40 | for i := range metrics { 41 | metrics[i].setDefaults() 42 | } 43 | } 44 | return nil 45 | } 46 | 47 | // Parse parse config from file 48 | func Parse(path string) (*Config, error) { 49 | b, err := os.ReadFile(path) 50 | if err != nil { 51 | return nil, err 52 | } 53 | var cfg Config 54 | if err = yaml.Unmarshal(b, &cfg); err != nil { 55 | return nil, err 56 | } 57 | if err = cfg.validateAndSetDefaults(); err != nil { 58 | return nil, err 59 | } 60 | 61 | return &cfg, nil 62 | } 63 | 64 | func getEnvOrDefault(key string, defaultVal string) string { 65 | val, ok := os.LookupEnv(key) 66 | if ok { 67 | return val 68 | } 69 | return defaultVal 70 | } 71 | -------------------------------------------------------------------------------- /pkg/client/services/ecs.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 5 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 6 | "github.com/go-kit/log" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | type ecsClient struct { 11 | *ecs.Client 12 | desc *prometheus.Desc 13 | logger log.Logger 14 | } 15 | 16 | func (c *ecsClient) Collect(namespace string, ch chan<- prometheus.Metric) error { 17 | if c.desc == nil { 18 | c.desc = newDescOfInstnaceInfo(namespace, "acs_ecs_dashboard", []string{"regionId", "instanceId", "instanceName", "hostname", "status"}) 19 | } 20 | 21 | req := ecs.CreateDescribeInstancesRequest() 22 | req.PageSize = requests.NewInteger(pageSize) 23 | resultChan := make(chan *result, 1<<10) 24 | var totalCount int 25 | 26 | go func() { 27 | defer close(resultChan) 28 | for hasNextPage, pageNum := true, 1; hasNextPage != false; pageNum++ { 29 | req.PageNumber = requests.NewInteger(pageNum) 30 | req.PageSize = requests.NewInteger(pageSize) 31 | response, err := c.DescribeInstances(req) 32 | if err != nil { 33 | resultChan <- &result{err: err} 34 | return 35 | } 36 | totalCount += len(response.Instances.Instance) 37 | if totalCount >= response.TotalCount { 38 | hasNextPage = false 39 | } 40 | for i := range response.Instances.Instance { 41 | resultChan <- &result{v: response.Instances.Instance[i]} 42 | } 43 | } 44 | }() 45 | for res := range resultChan { 46 | if res.err != nil { 47 | return res.err 48 | } 49 | ins := res.v.(ecs.Instance) 50 | ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, 1.0, 51 | ins.RegionId, ins.InstanceId, ins.InstanceName, ins.HostName, ins.Status) 52 | } 53 | return nil 54 | } 55 | 56 | func init() { 57 | register("acs_ecs_dashboard", func(s1, s2, s3 string, l log.Logger) (Collector, error) { 58 | client, err := ecs.NewClientWithAccessKey(s1, s2, s3) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return &ecsClient{Client: client, logger: l}, nil 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/client/services/clickhouse.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 5 | "github.com/aliyun/alibaba-cloud-sdk-go/services/clickhouse" 6 | "github.com/go-kit/log" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | type ckClient struct { 11 | *clickhouse.Client 12 | desc *prometheus.Desc 13 | logger log.Logger 14 | } 15 | 16 | func (c *ckClient) Collect(namespace string, ch chan<- prometheus.Metric) error { 17 | if c.desc == nil { 18 | c.desc = newDescOfInstnaceInfo(namespace, "acs_clickhouse", []string{"regionId", "dbclusterId", "type", "status", "desc"}) 19 | } 20 | 21 | req := clickhouse.CreateDescribeDBClustersRequest() 22 | req.PageSize = requests.NewInteger(pageSize) 23 | resultChan := make(chan *result, 1<<10) 24 | var totalCount int 25 | 26 | go func() { 27 | defer close(resultChan) 28 | for hasNextPage, pageNum := true, 1; hasNextPage != false; pageNum++ { 29 | req.PageNumber = requests.NewInteger(pageNum) 30 | req.PageSize = requests.NewInteger(pageSize) 31 | response, err := c.DescribeDBClusters(req) 32 | if err != nil { 33 | resultChan <- &result{err: err} 34 | return 35 | } 36 | totalCount += len(response.DBClusters.DBCluster) 37 | if totalCount >= response.TotalCount { 38 | hasNextPage = false 39 | } 40 | for i := range response.DBClusters.DBCluster { 41 | resultChan <- &result{v: response.DBClusters.DBCluster[i]} 42 | } 43 | } 44 | }() 45 | for res := range resultChan { 46 | if res.err != nil { 47 | return res.err 48 | } 49 | ins := res.v.(clickhouse.DBCluster) 50 | ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, 1.0, 51 | ins.RegionId, ins.DBClusterId, ins.DBClusterType, ins.DBClusterStatus, ins.DBClusterDescription) 52 | } 53 | return nil 54 | } 55 | 56 | func init() { 57 | register("acs_clickhouse", func(s1, s2, s3 string, l log.Logger) (Collector, error) { 58 | client, err := clickhouse.NewClientWithAccessKey(s1, s2, s3) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return &ckClient{Client: client, logger: l}, nil 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/client/services/polardb.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 5 | "github.com/aliyun/alibaba-cloud-sdk-go/services/polardb" 6 | "github.com/go-kit/log" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | type polardbClient struct { 11 | *polardb.Client 12 | desc *prometheus.Desc 13 | logger log.Logger 14 | } 15 | 16 | func (c *polardbClient) Collect(namespace string, ch chan<- prometheus.Metric) error { 17 | if c.desc == nil { 18 | c.desc = newDescOfInstnaceInfo(namespace, "acs_polardb", 19 | []string{"regionId", "dbclusterId", "role", "type", "status", "desc"}) 20 | } 21 | 22 | req := polardb.CreateDescribeDBClustersRequest() 23 | req.PageSize = requests.NewInteger(pageSize) 24 | resultChan := make(chan *result, 1<<10) 25 | var totalCount int 26 | 27 | go func() { 28 | defer close(resultChan) 29 | for hasNextPage, pageNum := true, 1; hasNextPage != false; pageNum++ { 30 | req.PageNumber = requests.NewInteger(pageNum) 31 | req.PageSize = requests.NewInteger(pageSize) 32 | response, err := c.DescribeDBClusters(req) 33 | if err != nil { 34 | resultChan <- &result{err: err} 35 | return 36 | } 37 | totalCount += len(response.Items.DBCluster) 38 | if totalCount >= response.TotalRecordCount { 39 | hasNextPage = false 40 | } 41 | for i := range response.Items.DBCluster { 42 | resultChan <- &result{v: response.Items.DBCluster[i]} 43 | } 44 | } 45 | }() 46 | for res := range resultChan { 47 | if res.err != nil { 48 | return res.err 49 | } 50 | ins := res.v.(polardb.DBCluster) 51 | ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, 1.0, 52 | ins.RegionId, ins.DBClusterId, ins.Role, ins.DBType, ins.DBClusterStatus, ins.DBClusterDescription) 53 | } 54 | return nil 55 | } 56 | 57 | func init() { 58 | register("acs_polardb", func(s1, s2, s3 string, l log.Logger) (Collector, error) { 59 | client, err := polardb.NewClientWithAccessKey(s1, s2, s3) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return &polardbClient{Client: client, logger: l}, nil 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/client/services/rds.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 5 | "github.com/aliyun/alibaba-cloud-sdk-go/services/rds" 6 | "github.com/go-kit/log" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | // Client wrap client 11 | type rdsClient struct { 12 | *rds.Client 13 | desc *prometheus.Desc 14 | logger log.Logger 15 | } 16 | 17 | func (c *rdsClient) Collect(namespace string, ch chan<- prometheus.Metric) error { 18 | if c.desc == nil { 19 | c.desc = newDescOfInstnaceInfo(namespace, "acs_rds_dashboard", 20 | []string{"regionId", "dbInstanceId", "name", "dbType", "desc", "status", "connection"}) 21 | } 22 | req := rds.CreateDescribeDBInstancesRequest() 23 | req.PageSize = requests.NewInteger(pageSize) 24 | resultChan := make(chan *result, 1<<10) 25 | var totalCount int 26 | 27 | go func() { 28 | defer close(resultChan) 29 | for hasNextPage, pageNum := true, 1; hasNextPage != false; pageNum++ { 30 | req.PageNumber = requests.NewInteger(pageNum) 31 | response, err := c.DescribeDBInstances(req) 32 | if err != nil { 33 | resultChan <- &result{err: err} 34 | return 35 | } 36 | totalCount += len(response.Items.DBInstance) 37 | if totalCount >= response.TotalRecordCount { 38 | hasNextPage = false 39 | } 40 | for i := range response.Items.DBInstance { 41 | resultChan <- &result{v: response.Items.DBInstance[i]} 42 | } 43 | } 44 | }() 45 | 46 | for res := range resultChan { 47 | if res.err != nil { 48 | return res.err 49 | } 50 | ins := res.v.(rds.DBInstance) 51 | ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, 1.0, 52 | ins.RegionId, ins.DBInstanceId, ins.DBInstanceName, ins.DBInstanceType, ins.DBInstanceDescription, ins.DBInstanceStatus, ins.ConnectionString) 53 | } 54 | return nil 55 | } 56 | 57 | func init() { 58 | register("acs_rds_dashboard", func(s1, s2, s3 string, l log.Logger) (Collector, error) { 59 | client, err := rds.NewClientWithAccessKey(s1, s2, s3) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return &rdsClient{Client: client, logger: l}, nil 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/client/services/r-kvstore.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 5 | r_kvstore "github.com/aliyun/alibaba-cloud-sdk-go/services/r-kvstore" 6 | "github.com/go-kit/log" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | type redisClient struct { 11 | *r_kvstore.Client 12 | desc *prometheus.Desc 13 | logger log.Logger 14 | } 15 | 16 | func (c *redisClient) Collect(namespace string, ch chan<- prometheus.Metric) error { 17 | if c.desc == nil { 18 | c.desc = newDescOfInstnaceInfo(namespace, "acs_kvstore", []string{"regionId", "instanceId", "name", "connectionDomain", "address", "status"}) 19 | } 20 | req := r_kvstore.CreateDescribeInstancesRequest() 21 | req.PageSize = requests.NewInteger(pageSize) 22 | resultChan := make(chan *result, 1<<10) 23 | var totalCount int 24 | 25 | go func() { 26 | defer close(resultChan) 27 | for hasNextPage, pageNum := true, 1; hasNextPage != false; pageNum++ { 28 | req.PageNumber = requests.NewInteger(pageNum) 29 | response, err := c.DescribeInstances(req) 30 | if err != nil { 31 | resultChan <- &result{err: err} 32 | return 33 | } 34 | totalCount += len(response.Instances.KVStoreInstance) 35 | if totalCount >= response.TotalCount { 36 | hasNextPage = false 37 | } 38 | for i := range response.Instances.KVStoreInstance { 39 | resultChan <- &result{v: response.Instances.KVStoreInstance[i]} 40 | } 41 | } 42 | }() 43 | 44 | for res := range resultChan { 45 | if res.err != nil { 46 | return res.err 47 | } 48 | ins := res.v.(r_kvstore.KVStoreInstanceInDescribeInstances) 49 | ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, 1.0, 50 | ins.RegionId, ins.InstanceId, ins.InstanceName, ins.ConnectionDomain, ins.PrivateIp, ins.InstanceStatus) 51 | } 52 | return nil 53 | } 54 | 55 | func init() { 56 | register("acs_kvstore", func(s1, s2, s3 string, l log.Logger) (Collector, error) { 57 | client, err := r_kvstore.NewClientWithAccessKey(s1, s2, s3) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return &redisClient{Client: client, logger: l}, nil 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/client/services/slb.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 5 | "github.com/aliyun/alibaba-cloud-sdk-go/services/slb" 6 | "github.com/go-kit/log" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | // Client wrap client 11 | type slbClient struct { 12 | *slb.Client 13 | desc *prometheus.Desc 14 | logger log.Logger 15 | } 16 | 17 | // Collect collect metrics 18 | func (c *slbClient) Collect(namespace string, ch chan<- prometheus.Metric) error { 19 | if c.desc == nil { 20 | c.desc = newDescOfInstnaceInfo(namespace, "acs_slb_dashboard", 21 | []string{"regionId", "instanceId", "name", "address", "type", "status"}) 22 | } 23 | req := slb.CreateDescribeLoadBalancersRequest() 24 | req.PageSize = requests.NewInteger(pageSize) 25 | resultChan := make(chan *result, 1<<10) 26 | var totalCount int 27 | 28 | go func() { 29 | defer close(resultChan) 30 | for hasNextPage, pageNum := true, 1; hasNextPage != false; pageNum++ { 31 | req.PageNumber = requests.NewInteger(pageNum) 32 | response, err := c.DescribeLoadBalancers(req) 33 | if err != nil { 34 | resultChan <- &result{err: err} 35 | return 36 | } 37 | totalCount += len(response.LoadBalancers.LoadBalancer) 38 | if totalCount >= response.TotalCount { 39 | hasNextPage = false 40 | } 41 | for i := range response.LoadBalancers.LoadBalancer { 42 | resultChan <- &result{v: response.LoadBalancers.LoadBalancer[i]} 43 | } 44 | } 45 | }() 46 | 47 | for res := range resultChan { 48 | if res.err != nil { 49 | return res.err 50 | } 51 | ins := res.v.(slb.LoadBalancer) 52 | ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, 1.0, 53 | ins.RegionId, ins.LoadBalancerId, ins.LoadBalancerName, ins.Address, ins.SpecType, ins.LoadBalancerStatus) 54 | } 55 | return nil 56 | } 57 | 58 | func init() { 59 | register("acs_slb_dashboard", func(s1, s2, s3 string, l log.Logger) (Collector, error) { 60 | client, err := slb.NewClientWithAccessKey(s1, s2, s3) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return &slbClient{Client: client, logger: l}, nil 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /pkg/collector/instanceinfo.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | "time" 7 | 8 | "github.com/go-kit/log" 9 | "github.com/go-kit/log/level" 10 | "github.com/prometheus/client_golang/prometheus" 11 | 12 | "github.com/fengxsong/aliyun_exporter/pkg/client" 13 | "github.com/fengxsong/aliyun_exporter/pkg/config" 14 | ) 15 | 16 | type instanceInfoCollector struct { 17 | namespace string 18 | cfg *config.Config 19 | logger log.Logger 20 | client *client.ServiceClient 21 | lock sync.Mutex 22 | } 23 | 24 | // NewInstanceInfoCollector create a new collector for instance info 25 | func NewInstanceInfoCollector(namespace string, cfg *config.Config, rt http.RoundTripper, logger log.Logger) (prometheus.Collector, error) { 26 | if logger == nil { 27 | logger = log.NewNopLogger() 28 | } 29 | cli, err := client.NewServiceClient(cfg.AccessKey, cfg.AccessKeySecret, cfg.InstanceInfo.Regions, rt, logger) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return &instanceInfoCollector{ 34 | namespace: namespace, 35 | cfg: cfg, 36 | logger: logger, 37 | client: cli, 38 | }, nil 39 | } 40 | 41 | func (c *instanceInfoCollector) Describe(ch chan<- *prometheus.Desc) {} 42 | 43 | func (c *instanceInfoCollector) Collect(ch chan<- prometheus.Metric) { 44 | c.lock.Lock() 45 | defer c.lock.Unlock() 46 | wg := &sync.WaitGroup{} 47 | for i := range c.cfg.InstanceInfo.Types { 48 | for j := range c.cfg.InstanceInfo.Regions { 49 | wg.Add(1) 50 | go func(sub string, region string) { 51 | start := time.Now() 52 | defer func() { 53 | wg.Done() 54 | scrapeDuration.WithLabelValues(sub, "InstanceInfo", region).Observe(time.Since(start).Seconds()) 55 | }() 56 | if err := c.client.Collect(c.namespace, sub, region, ch); err != nil { 57 | level.Error(c.logger).Log("err", err, "instancetype", sub) 58 | scrapeTotal.WithLabelValues(sub, "InstanceInfo", region, "failed").Inc() 59 | return 60 | } 61 | scrapeTotal.WithLabelValues(sub, "InstanceInfo", region, "success").Inc() 62 | }(c.cfg.InstanceInfo.Types[i], c.cfg.InstanceInfo.Regions[j]) 63 | } 64 | } 65 | wg.Wait() 66 | } 67 | -------------------------------------------------------------------------------- /pkg/config/metric.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | // Metric meta 12 | type Metric struct { 13 | Name string `yaml:"name"` 14 | Alias string `yaml:"alias,omitempty"` 15 | Period string `yaml:"period,omitempty"` 16 | Description string `yaml:"desc,omitempty"` 17 | Dimensions []string `yaml:"dimensions,omitempty"` 18 | Unit string `yaml:"unit,omitempty"` 19 | Measure string `yaml:"measure,omitempty"` 20 | Format bool `yaml:"format,omitempty"` 21 | 22 | once sync.Once 23 | desc *prometheus.Desc 24 | } 25 | 26 | // setdefault options 27 | func (m *Metric) setDefaults() { 28 | if m.Period == "" { 29 | m.Period = "60" 30 | } 31 | if m.Description == "" { 32 | m.Description = m.Name 33 | } 34 | // Do some fallback in case someone runs this exporter directly 35 | // without modifying the example configuration 36 | periods := strings.Split(m.Period, ",") 37 | m.Period = periods[0] 38 | measures := strings.Split(m.Measure, ",") 39 | m.Measure = measures[0] 40 | switch m.Measure { 41 | case "Maximum", "Minimum", "Average", "Value": 42 | default: 43 | m.Measure = "Average" 44 | } 45 | m.Description = fmt.Sprintf("%s unit:%s measure:%s", m.Description, m.Unit, m.Measure) 46 | } 47 | 48 | var formalizeMetricName = strings.NewReplacer(".", "_", "-", "_").Replace 49 | 50 | // String name of metric 51 | func (m *Metric) String() string { 52 | if m.Alias != "" { 53 | return m.Alias 54 | } 55 | if m.Format { 56 | return strings.Join([]string{m.Name, formatUnit(m.Unit)}, "_") 57 | } 58 | return formalizeMetricName(m.Name) 59 | } 60 | 61 | // Desc to prometheus desc 62 | func (m *Metric) Desc(namespace, sub string, dimensions ...string) *prometheus.Desc { 63 | if len(m.Dimensions) == 0 { 64 | m.Dimensions = dimensions 65 | } 66 | // TODO: if length of dimemsions is changed 67 | m.once.Do(func() { 68 | m.desc = prometheus.NewDesc( 69 | prometheus.BuildFQName(namespace, sub, m.String()), 70 | m.Description, 71 | m.Dimensions, 72 | nil, 73 | ) 74 | }) 75 | return m.desc 76 | } 77 | 78 | var durationUnitMapping = map[string]string{ 79 | "s": "second", 80 | "m": "minute", 81 | "h": "hour", 82 | "d": "day", 83 | } 84 | 85 | func formatUnit(s string) string { 86 | s = strings.TrimSpace(s) 87 | if s == "%" { 88 | return "percent" 89 | } 90 | 91 | if strings.IndexAny(s, "/") > 0 { 92 | fields := strings.Split(s, "/") 93 | if len(fields) == 2 { 94 | if v, ok := durationUnitMapping[fields[1]]; ok { 95 | return strings.ToLower(strings.Join([]string{fields[0], "per", v}, "_")) 96 | } 97 | } 98 | } 99 | return strings.ToLower(s) 100 | } 101 | -------------------------------------------------------------------------------- /pkg/collector/collector.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | "time" 7 | 8 | "github.com/go-kit/log" 9 | "github.com/go-kit/log/level" 10 | "github.com/prometheus/client_golang/prometheus" 11 | 12 | "github.com/fengxsong/aliyun_exporter/pkg/client" 13 | "github.com/fengxsong/aliyun_exporter/pkg/config" 14 | ) 15 | 16 | const app = "aliyun_exporter" 17 | 18 | func Name() string { return app } 19 | 20 | var ( 21 | scrapeDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ 22 | Namespace: app, 23 | Name: "scrape_duration_seconds", 24 | Help: "Duration of each metrics scraping"}, 25 | []string{"namespace", "collector", "region"}) 26 | scrapeTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ 27 | Namespace: app, 28 | Name: "scrape_total", 29 | Help: "Total scrape counts", 30 | }, []string{"namespace", "collector", "region", "state"}) 31 | ) 32 | 33 | func init() { 34 | prometheus.MustRegister(scrapeDuration, scrapeTotal) 35 | } 36 | 37 | // cloudMonitor .. 38 | type cloudMonitor struct { 39 | // namespace is the prefix of all registered metrics 40 | namespace string 41 | cfg *config.Config 42 | logger log.Logger 43 | client *client.MetricClient 44 | lock sync.Mutex 45 | } 46 | 47 | // NewCloudMonitorCollector create a new collector for cloud monitor 48 | func NewCloudMonitorCollector(namespace string, cfg *config.Config, rt http.RoundTripper, logger log.Logger) (prometheus.Collector, error) { 49 | if logger == nil { 50 | logger = log.NewNopLogger() 51 | } 52 | cli, err := client.NewMetricClient(cfg.AccessKey, cfg.AccessKeySecret, cfg.Region, rt, logger) 53 | if err != nil { 54 | return nil, err 55 | } 56 | c := &cloudMonitor{ 57 | namespace: namespace, 58 | cfg: cfg, 59 | logger: logger, 60 | client: cli, 61 | } 62 | return c, nil 63 | } 64 | 65 | func (m *cloudMonitor) Describe(ch chan<- *prometheus.Desc) {} 66 | 67 | func (m *cloudMonitor) Collect(ch chan<- prometheus.Metric) { 68 | m.lock.Lock() 69 | defer m.lock.Unlock() 70 | 71 | wg := &sync.WaitGroup{} 72 | for sub, metrics := range m.cfg.Metrics { 73 | for i := range metrics { 74 | wg.Add(1) 75 | go func(namespace string, metric *config.Metric) { 76 | start := time.Now() 77 | defer func() { 78 | scrapeDuration.WithLabelValues(namespace, metric.String(), m.cfg.Region).Observe(time.Since(start).Seconds()) 79 | wg.Done() 80 | }() 81 | if err := m.client.Collect(m.namespace, namespace, metric, ch); err != nil { 82 | level.Error(m.logger).Log("err", err, "sub", namespace, "metric", metric.String()) 83 | scrapeTotal.WithLabelValues(namespace, metric.String(), m.cfg.Region, "failed").Inc() 84 | return 85 | } 86 | scrapeTotal.WithLabelValues(namespace, metric.String(), m.cfg.Region, "success").Inc() 87 | }(sub, metrics[i]) 88 | } 89 | } 90 | wg.Wait() 91 | } 92 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | 8 | "github.com/alecthomas/kingpin/v2" 9 | "github.com/go-kit/log/level" 10 | "github.com/prometheus/common/promlog" 11 | "gopkg.in/yaml.v3" 12 | 13 | "github.com/fengxsong/aliyun_exporter/pkg/client" 14 | "github.com/fengxsong/aliyun_exporter/pkg/ratelimit" 15 | ) 16 | 17 | type flagGroup interface { 18 | Flag(string, string) *kingpin.FlagClause 19 | } 20 | 21 | type command interface { 22 | help() string 23 | run(*kingpin.ParseContext) error 24 | } 25 | 26 | var commands = map[string]command{} 27 | 28 | func AddCommands(app *kingpin.Application) { 29 | for name, c := range commands { 30 | cc := app.Command(name, c.help()) 31 | if flagRegisterer, ok := c.(interface { 32 | addFlags(flagGroup) 33 | }); ok { 34 | flagRegisterer.addFlags(cc) 35 | } 36 | cc.Action(c.run) 37 | } 38 | } 39 | 40 | func init() { 41 | commands["generate-example-config"] = &generateExampleConfigOption{} 42 | } 43 | 44 | func rateLimitFlag(f flagGroup) *int { 45 | return f.Flag("rate", "RPS/request per second to alibaba cloudmonitor service").Default("50").Int() 46 | } 47 | 48 | type generateExampleConfigOption struct { 49 | out string 50 | ak, sk string 51 | region string 52 | includes []string 53 | listing bool 54 | rate *int 55 | promlogConfig *promlog.Config 56 | } 57 | 58 | func (o *generateExampleConfigOption) help() string { return "Print example of config file" } 59 | 60 | func (o *generateExampleConfigOption) addFlags(f flagGroup) { 61 | f.Flag("out", "Filepath to write example config to").Default("").Short('o').StringVar(&o.out) 62 | f.Flag("accesskey", "Access key").Envar("ALIBABA_CLOUD_ACCESS_KEY").Required().StringVar(&o.ak) 63 | f.Flag("accesskeysecret", "Access key secret").Envar("ALIBABA_CLOUD_ACCESS_KEY_SECRET").Required().StringVar(&o.sk) 64 | f.Flag("region", "Region id").Default("cn-hangzhou").StringVar(&o.region) 65 | f.Flag("includes", "Only print metrics list of specified namespaces, default will print all").Default("").StringsVar(&o.includes) 66 | f.Flag("list", "List avaliable namespaces of metrics only").Default("false").Short('l').BoolVar(&o.listing) 67 | 68 | o.promlogConfig = addPromlogConfig(f) 69 | o.rate = rateLimitFlag(f) 70 | } 71 | 72 | func (o *generateExampleConfigOption) run(_ *kingpin.ParseContext) error { 73 | rt := ratelimit.New(*o.rate) 74 | logger := promlog.New(o.promlogConfig) 75 | mc, err := client.NewMetricClient(o.ak, o.sk, o.region, rt, logger) 76 | if err != nil { 77 | return err 78 | } 79 | if o.listing { 80 | namespaces, err := mc.ListingNamespace() 81 | if err != nil { 82 | return err 83 | } 84 | level.Info(logger).Log("msg", fmt.Sprintf("available namespaces are %s", namespaces)) 85 | return nil 86 | } 87 | if len(o.includes) == 1 && o.includes[0] == "" { 88 | o.includes = []string{} 89 | } 90 | cfg, err := mc.GenerateExampleConfig(o.ak, o.sk, o.region, o.includes...) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | var writer io.Writer 96 | switch o.out { 97 | case "", "stdout": 98 | writer = os.Stdout 99 | default: 100 | writer, err = os.Create(o.out) 101 | if err != nil { 102 | return err 103 | } 104 | } 105 | encoder := yaml.NewEncoder(writer) 106 | encoder.SetIndent(2) 107 | if err = encoder.Encode(&cfg); err != nil { 108 | return err 109 | } 110 | level.Info(logger).Log("msg", "example configurations have been successfully generated, please modify the corresponding 'period'/'measure'/'instance_types' fields before running.") 111 | return nil 112 | } 113 | -------------------------------------------------------------------------------- /cmd/serve.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "net/http" 5 | _ "net/http/pprof" 6 | "os" 7 | "os/signal" 8 | "runtime" 9 | "strings" 10 | "syscall" 11 | 12 | "github.com/alecthomas/kingpin/v2" 13 | "github.com/go-kit/log/level" 14 | "github.com/prometheus/client_golang/prometheus" 15 | "github.com/prometheus/client_golang/prometheus/promhttp" 16 | "github.com/prometheus/common/promlog" 17 | promlogflag "github.com/prometheus/common/promlog/flag" 18 | "github.com/prometheus/common/version" 19 | "github.com/prometheus/exporter-toolkit/web" 20 | 21 | "github.com/fengxsong/aliyun_exporter/pkg/collector" 22 | "github.com/fengxsong/aliyun_exporter/pkg/config" 23 | "github.com/fengxsong/aliyun_exporter/pkg/ratelimit" 24 | ) 25 | 26 | type serveOption struct { 27 | configFile string 28 | metricsPath string 29 | rate *int 30 | promlogConfig *promlog.Config 31 | webflagConfig *web.FlagConfig 32 | } 33 | 34 | func init() { 35 | commands["serve"] = &serveOption{} 36 | } 37 | 38 | func addPromlogConfig(f flagGroup) *promlog.Config { 39 | promlogConfig := &promlog.Config{ 40 | Level: &promlog.AllowedLevel{}, 41 | Format: &promlog.AllowedFormat{}, 42 | } 43 | f.Flag(promlogflag.LevelFlagName, promlogflag.LevelFlagHelp).Default("info").HintOptions(promlog.LevelFlagOptions...).SetValue(promlogConfig.Level) 44 | f.Flag(promlogflag.FormatFlagName, promlogflag.FormatFlagHelp).Default("logfmt").HintOptions(promlog.FormatFlagOptions...).SetValue(promlogConfig.Format) 45 | 46 | return promlogConfig 47 | } 48 | 49 | func (o *serveOption) help() string { return "Run http metrics handlers" } 50 | 51 | func (o *serveOption) addFlags(f flagGroup) { 52 | f.Flag("config", "Filepath of config").Default("config.yaml").Short('c').StringVar(&o.configFile) 53 | f.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").StringVar(&o.metricsPath) 54 | 55 | o.promlogConfig = addPromlogConfig(f) 56 | o.webflagConfig = wrapWebFlagConfig(f, ":9527") 57 | o.rate = rateLimitFlag(f) 58 | } 59 | 60 | func wrapWebFlagConfig(f flagGroup, defaultAddress string) *web.FlagConfig { 61 | systemdSocket := func() *bool { b := false; return &b }() // Socket activation only available on Linux 62 | if runtime.GOOS == "linux" { 63 | systemdSocket = f.Flag( 64 | "web.systemd-socket", 65 | "Use systemd socket activation listeners instead of port listeners (Linux only).", 66 | ).Bool() 67 | } 68 | flags := web.FlagConfig{ 69 | WebListenAddresses: f.Flag( 70 | "web.listen-address", 71 | "Addresses on which to expose metrics and web interface. Repeatable for multiple addresses.", 72 | ).Default(defaultAddress).Strings(), 73 | WebSystemdSocket: systemdSocket, 74 | WebConfigFile: f.Flag( 75 | "web.config.file", 76 | "Path to configuration file that can enable TLS or authentication. See: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md", 77 | ).Default("").String(), 78 | } 79 | return &flags 80 | } 81 | 82 | func (o *serveOption) run(_ *kingpin.ParseContext) error { 83 | cfg, err := config.Parse(o.configFile) 84 | if err != nil { 85 | return err 86 | } 87 | logger := promlog.New(o.promlogConfig) 88 | rt := ratelimit.New(*o.rate) 89 | cmc, err := collector.NewCloudMonitorCollector("cloudmonitor", cfg, rt, logger) 90 | if err != nil { 91 | return err 92 | } 93 | prometheus.MustRegister(cmc) 94 | 95 | if cfg.InstanceInfo != nil { 96 | level.Info(logger).Log("msg", "enabling instance info collectors for lable joining", 97 | "collectors", strings.Join(cfg.InstanceInfo.Types, ", "), 98 | "regions", strings.Join(cfg.InstanceInfo.Regions, ", "), 99 | ) 100 | iic, err := collector.NewInstanceInfoCollector("cloudmonitor", cfg, rt, logger) 101 | if err != nil { 102 | return err 103 | } 104 | prometheus.MustRegister(iic) 105 | } 106 | 107 | http.Handle(o.metricsPath, promhttp.InstrumentMetricHandler( 108 | prometheus.DefaultRegisterer, 109 | promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{MaxRequestsInFlight: 1}), 110 | )) 111 | 112 | healthzPath := "/-/healthy" 113 | http.HandleFunc(healthzPath, func(w http.ResponseWriter, r *http.Request) { 114 | w.WriteHeader(http.StatusOK) 115 | w.Write([]byte("Healthy")) 116 | }) 117 | 118 | landingConfig := web.LandingConfig{ 119 | Name: collector.Name(), 120 | Description: "a prometheus exporter for scraping metrics from alibaba cloudmonitor services", 121 | Version: version.Info(), 122 | Links: []web.LandingLinks{ 123 | { 124 | Address: o.metricsPath, 125 | Text: "Metrics", 126 | Description: "endpoint for scraping metrics", 127 | }, 128 | { 129 | Address: "/debug/pprof", 130 | Text: "Pprof", 131 | Description: "pprof handlers", 132 | }, 133 | }, 134 | } 135 | 136 | landingpage, err := web.NewLandingPage(landingConfig) 137 | if err != nil { 138 | return err 139 | } 140 | http.Handle("/", landingpage) 141 | 142 | srv := &http.Server{} 143 | srvc := make(chan error) 144 | term := make(chan os.Signal, 1) 145 | signal.Notify(term, os.Interrupt, syscall.SIGTERM) 146 | 147 | go func() { 148 | if err := web.ListenAndServe(srv, o.webflagConfig, logger); err != nil { 149 | level.Error(logger).Log("msg", "Error starting HTTP server", "err", err) 150 | srvc <- err 151 | close(srvc) 152 | } 153 | }() 154 | 155 | for { 156 | select { 157 | case <-term: 158 | level.Info(logger).Log("msg", "Received SIGTERM, exiting gracefully...") 159 | return nil 160 | case err = <-srvc: 161 | return err 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /deploy/rules.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: slb 3 | rules: 4 | - alert: slb_5xx_percent:critical 5 | expr: |- 6 | sum(cloudmonitor_acs_slb_dashboard_StatusCode5xx) by (account_name,provider,instanceId, port) / 7 | sum(cloudmonitor_acs_slb_dashboard_Qps) by (account_name,provider,instanceId, port) > 0.05 8 | labels: 9 | severity: critical 10 | for: 5m 11 | annotations: 12 | summary: "{{ $labels.account_name }}{{ $labels.provider }} SLB {{ $labels.instanceId }}:{{ $labels.port }} 5xx percent > 5%" 13 | - alert: slb_response_time:critical 14 | expr: |- 15 | avg(cloudmonitor_acs_slb_dashboard_Rt) by (account_name,provider,instanceId, port) > 1000 16 | labels: 17 | severity: warning 18 | for: 5m 19 | annotations: 20 | summary: "{{ $labels.account_name }}{{ $labels.provider }} SLB {{ $labels.instanceId }}:{{ $labels.port }} RT > 1000ms" 21 | - alert: slb_tx_traffic_drop_percent:critical 22 | expr: |- 23 | sum(cloudmonitor_acs_slb_dashboard_DropTrafficTX) by (account_name,provider,instanceId, port) / 24 | sum(cloudmonitor_acs_slb_dashboard_TrafficTXNew) by (account_name,provider,instanceId, port) > 0.001 25 | labels: 26 | severity: critical 27 | for: 5m 28 | annotations: 29 | summary: "{{ $labels.account_name }}{{ $labels.provider }} SLB {{ $labels.instanceId }}:{{ $labels.port }} tx traffic drop percent > 0.1%" 30 | - alert: slb_rx_traffic_drop_percent:critical 31 | expr: |- 32 | sum(cloudmonitor_acs_slb_dashboard_DropTrafficRX) by (account_name,provider,instanceId, port) / 33 | sum(cloudmonitor_acs_slb_dashboard_TrafficRXNew) by (account_name,provider,instanceId, port) > 0.001 34 | labels: 35 | severity: critical 36 | for: 5m 37 | annotations: 38 | summary: "{{ $labels.account_name }}{{ $labels.provider }} SLB {{ $labels.instanceId }}:{{ $labels.port }} rx traffic drop percent > 0.1%" 39 | 40 | - name: rds 41 | rules: 42 | - alert: rds_cpu_pressure:high 43 | expr: |- 44 | sum(cloudmonitor_acs_rds_dashboard_CpuUsage 45 | * on (instanceId) group_left(desc,regionId) 46 | label_replace(cloudmonitor_acs_rds_dashboard_instance_info, "instanceId", "$1", "dbInstanceId", "(.*)")) 47 | without (instance, userId, job) > 95 48 | labels: 49 | severity: critical 50 | for: 5m 51 | annotations: 52 | summary: "{{ $labels.account_name }}{{ $labels.provider }} RDS {{ $labels.desc }} under high cpu pressure > 95%" 53 | - alert: rds_memory_pressure:high 54 | expr: |- 55 | sum(cloudmonitor_acs_rds_dashboard_MemoryUsage 56 | * on (instanceId) group_left(desc,regionId) 57 | label_replace(cloudmonitor_acs_rds_dashboard_instance_info, "instanceId", "$1", "dbInstanceId", "(.*)")) 58 | without (instance, userId, job) > 95 59 | labels: 60 | severity: critical 61 | for: 5m 62 | annotations: 63 | summary: "{{ $labels.account_name }}{{ $labels.provider }} RDS {{ $labels.desc }} under high memory pressure > 95%" 64 | - alert: rds_iops_pressure:critical 65 | expr: |- 66 | sum(cloudmonitor_acs_rds_dashboard_IOPSUsage 67 | * on (instanceId) group_left(desc,regionId) 68 | label_replace(cloudmonitor_acs_rds_dashboard_instance_info, "instanceId", "$1", "dbInstanceId", "(.*)")) 69 | without (instance, userId, job) > 90 70 | labels: 71 | severity: critical 72 | for: 5m 73 | annotations: 74 | summary: "{{ $labels.account_name }}{{ $labels.provider }} RDS {{ $labels.desc }} under high iops pressure > 90%" 75 | - alert: rds_disk_space_exhausted:critical 76 | expr: |- 77 | sum(cloudmonitor_acs_rds_dashboard_DiskUsage 78 | * on (instanceId) group_left(desc,regionId) 79 | label_replace(cloudmonitor_acs_rds_dashboard_instance_info, "instanceId", "$1", "dbInstanceId", "(.*)")) 80 | without (instance, userId, job) > 95 81 | labels: 82 | severity: critical 83 | for: 5m 84 | annotations: 85 | summary: "{{ $labels.account_name }}{{ $labels.provider }} RDS {{ $labels.DBInstanceDescription }} disk space will be exhausted soon > 95%" 86 | - alert: rds_connection_pressure:critical 87 | expr: |- 88 | sum(cloudmonitor_acs_rds_dashboard_ConnectionUsage 89 | * on (instanceId) group_left(desc,regionId) 90 | label_replace(cloudmonitor_acs_rds_dashboard_instance_info, "instanceId", "$1", "dbInstanceId", "(.*)")) 91 | without (instance, userId, job) > 95 92 | labels: 93 | severity: critical 94 | for: 5m 95 | annotations: 96 | summary: "{{ $labels.account_name }}{{ $labels.provider }} RDS {{ $labels.desc }} connection usage > 95%" 97 | - alert: rds_slow_queries:critical 98 | expr: |- 99 | sum(cloudmonitor_acs_rds_dashboard_MySQL_SlowQueries 100 | * on (instanceId) group_left(desc,regionId) 101 | label_replace(cloudmonitor_acs_rds_dashboard_instance_info, "instanceId", "$1", "dbInstanceId", "(.*)")) 102 | without (instance, userId, job) > 10 103 | labels: 104 | severity: critical 105 | for: 5m 106 | annotations: 107 | summary: "{{ $labels.account_name }}{{ $labels.provider }} RDS {{ $labels.desc }} slow queries > 10 counts/second" 108 | 109 | - name: redis 110 | rules: 111 | - alert: redis_cpu_pressure:critical 112 | expr: |- 113 | sum(cloudmonitor_acs_kvstore_CpuUsage 114 | * on (instanceId) group_left(address,name,regionId) 115 | label_replace(cloudmonitor_acs_kvstore_instance_info, "instanceId", "$1", "instanceId", "(.*)")) 116 | without (instance, userId, job) > 95 117 | labels: 118 | severity: critical 119 | for: 5m 120 | annotations: 121 | summary: "{{ $labels.account_name }}{{ $labels.provider }} Redis {{ $labels.InstanceName }} under high cpu pressure > 95%" 122 | - alert: redis_memory_pressure:critical 123 | expr: |- 124 | sum(cloudmonitor_acs_kvstore_MemoryUsage 125 | * on (instanceId) group_left(address,name,regionId) 126 | label_replace(cloudmonitor_acs_kvstore_instance_info, "instanceId", "$1", "instanceId", "(.*)")) 127 | without (instance, userId, job) > 95 128 | labels: 129 | severity: critical 130 | for: 5m 131 | annotations: 132 | summary: "{{ $labels.account_name }}{{ $labels.provider }} Redis {{ $labels.InstanceName }} memory usage > 95%" 133 | - alert: redis_connection_pressure:high 134 | expr: |- 135 | sum(cloudmonitor_acs_kvstore_ConnectionUsage 136 | * on (instanceId) group_left(address,name,regionId) 137 | label_replace(cloudmonitor_acs_kvstore_instance_info, "instanceId", "$1", "instanceId", "(.*)")) 138 | without (instance, userId, job) > 95 139 | labels: 140 | severity: critical 141 | for: 5m 142 | annotations: 143 | summary: "{{ $labels.account_name }}{{ $labels.provider }} Redis {{ $labels.InstanceName }} connection usage > 95%" 144 | -------------------------------------------------------------------------------- /pkg/client/metric.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "sort" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | 12 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 13 | "github.com/aliyun/alibaba-cloud-sdk-go/services/cms" 14 | "github.com/go-kit/log" 15 | "github.com/go-kit/log/level" 16 | "github.com/prometheus/client_golang/prometheus" 17 | 18 | "github.com/fengxsong/aliyun_exporter/pkg/client/services" 19 | "github.com/fengxsong/aliyun_exporter/pkg/config" 20 | ) 21 | 22 | var ignores = map[string]struct{}{ 23 | "timestamp": {}, 24 | "Maximum": {}, 25 | "Minimum": {}, 26 | "Average": {}, 27 | } 28 | 29 | // Datapoint datapoint 30 | type Datapoint map[string]interface{} 31 | 32 | // Get return value for measure 33 | func (d Datapoint) Get(measure string) float64 { 34 | v, ok := d[measure] 35 | if !ok { 36 | return 0 37 | } 38 | return v.(float64) 39 | } 40 | 41 | // Labels return labels that not in ignores 42 | func (d Datapoint) Labels() []string { 43 | labels := make([]string, 0) 44 | for k := range d { 45 | if _, ok := ignores[k]; !ok { 46 | labels = append(labels, k) 47 | } 48 | } 49 | sort.Strings(labels) 50 | return labels 51 | } 52 | 53 | // Values return values for lables 54 | func (d Datapoint) Values(labels ...string) []string { 55 | values := make([]string, 0, len(labels)) 56 | for i := range labels { 57 | values = append(values, fmt.Sprintf("%s", d[labels[i]])) 58 | } 59 | return values 60 | } 61 | 62 | // MetricClient wrap cms.client 63 | type MetricClient struct { 64 | cms *cms.Client 65 | logger log.Logger 66 | } 67 | 68 | // NewMetricClient create metric Client 69 | func NewMetricClient(ak, secret, region string, rt http.RoundTripper, logger log.Logger) (*MetricClient, error) { 70 | cmsClient, err := cms.NewClientWithAccessKey(region, ak, secret) 71 | if err != nil { 72 | return nil, err 73 | } 74 | cmsClient.SetTransport(rt) 75 | if logger == nil { 76 | logger = log.NewNopLogger() 77 | } 78 | return &MetricClient{cmsClient, logger}, nil 79 | } 80 | 81 | // retrive get datapoints for metric 82 | // TODO: can we using BatchExport function instead? seems like this function returned a time-range metrics series 83 | func (c *MetricClient) retrive(sub string, name string, period string) ([]Datapoint, error) { 84 | var ret []Datapoint 85 | 86 | var ( 87 | firstRun = true 88 | nextToken string 89 | ) 90 | 91 | for { 92 | if !firstRun && nextToken == "" { 93 | break 94 | } 95 | req := cms.CreateDescribeMetricLastRequest() 96 | req.Namespace = sub 97 | req.MetricName = name 98 | req.Period = period 99 | req.NextToken = nextToken 100 | resp, err := c.cms.DescribeMetricLast(req) 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | var datapoints []Datapoint 106 | if err = json.Unmarshal([]byte(resp.Datapoints), &datapoints); err != nil { 107 | // some unexpected error 108 | level.Error(c.logger).Log("content", resp.GetHttpContentString(), "error", err) 109 | return nil, err 110 | } 111 | ret = append(ret, datapoints...) 112 | nextToken = resp.NextToken 113 | firstRun = false 114 | } 115 | 116 | return ret, nil 117 | } 118 | 119 | // Collect do collect metrics into channel 120 | func (c *MetricClient) Collect(namespace string, sub string, m *config.Metric, ch chan<- prometheus.Metric) error { 121 | if m.Name == "" { 122 | level.Warn(c.logger).Log("msg", "metric name must been set") 123 | return nil 124 | } 125 | datapoints, err := c.retrive(sub, m.Name, m.Period) 126 | if err != nil { 127 | return err 128 | } 129 | for _, dp := range datapoints { 130 | val := dp.Get(m.Measure) 131 | ch <- prometheus.MustNewConstMetric( 132 | m.Desc(namespace, sub, dp.Labels()...), 133 | prometheus.GaugeValue, 134 | val, 135 | dp.Values(m.Dimensions...)..., 136 | ) 137 | } 138 | return nil 139 | } 140 | 141 | // TODO: is there any convenient way to list all those namespaces? 142 | func (c *MetricClient) ListingNamespace() ([]string, error) { 143 | resources, err := c.describeMetricMetaListWithNamespace("") 144 | if err != nil { 145 | return nil, err 146 | } 147 | m := make(map[string]struct{}) 148 | var ret []string 149 | for _, res := range resources { 150 | if _, ok := m[res.Namespace]; !ok { 151 | m[res.Namespace] = struct{}{} 152 | ret = append(ret, res.Namespace) 153 | } 154 | } 155 | sort.Strings(ret) 156 | return ret, nil 157 | } 158 | 159 | func (c *MetricClient) describeMetricMetaListWithNamespace(namespace string) ([]cms.Resource, error) { 160 | var ret []cms.Resource 161 | pageNumber := 1 162 | for { 163 | req := cms.CreateDescribeMetricMetaListRequest() 164 | if namespace != "" { 165 | req.Namespace = namespace 166 | } 167 | req.PageSize = requests.NewInteger(1 << 8) 168 | req.PageNumber = requests.NewInteger(pageNumber) 169 | resp, err := c.cms.DescribeMetricMetaList(req) 170 | if err != nil { 171 | return nil, err 172 | } 173 | level.Debug(c.logger).Log("response", resp.GetHttpContentString()) 174 | totalCount, err := strconv.Atoi(resp.TotalCount) 175 | if err != nil { 176 | return nil, err 177 | } 178 | ret = append(ret, resp.Resources.Resource...) 179 | pageNumber++ 180 | if len(ret) >= totalCount { 181 | break 182 | } 183 | } 184 | return ret, nil 185 | } 186 | 187 | func (c *MetricClient) describeMetricMetaList(includes ...string) ([]cms.Resource, error) { 188 | if len(includes) == 0 { 189 | return c.describeMetricMetaListWithNamespace("") 190 | } 191 | 192 | type metricMetaResult struct { 193 | resources []cms.Resource 194 | err error 195 | } 196 | 197 | ch := make(chan *metricMetaResult, len(includes)) 198 | wg := &sync.WaitGroup{} 199 | for i := range includes { 200 | wg.Add(1) 201 | go func(namespace string) { 202 | defer wg.Done() 203 | res, err := c.describeMetricMetaListWithNamespace(namespace) 204 | if err != nil { 205 | close(ch) 206 | ch <- &metricMetaResult{err: err} 207 | return 208 | } 209 | ch <- &metricMetaResult{resources: res} 210 | }(includes[i]) 211 | } 212 | wg.Wait() 213 | close(ch) 214 | 215 | var ret []cms.Resource 216 | for res := range ch { 217 | if res.err != nil { 218 | return nil, res.err 219 | } 220 | ret = append(ret, res.resources...) 221 | } 222 | return ret, nil 223 | } 224 | 225 | // GenerateExampleConfig create example config 226 | func (c *MetricClient) GenerateExampleConfig(ak, sk, region string, includes ...string) (*config.Config, error) { 227 | metas, err := c.describeMetricMetaList(includes...) 228 | if err != nil { 229 | return nil, err 230 | } 231 | cfg := &config.Config{ 232 | AccessKey: "", 233 | AccessKeySecret: "", 234 | Region: region, 235 | Metrics: make(map[string][]*config.Metric), 236 | InstanceInfo: &config.InstanceInfo{}, 237 | } 238 | 239 | builtin := services.Names() 240 | if len(includes) == 0 { 241 | level.Info(c.logger).Log("msg", "no collectors specified, using builtin instance info collectors will be used", "collectors", strings.Join(builtin, ", ")) 242 | cfg.InstanceInfo.Types = builtin 243 | } else { 244 | tmp := make(map[string]struct{}) 245 | for i := range builtin { 246 | tmp[builtin[i]] = struct{}{} 247 | } 248 | var unsupported []string 249 | for i := range includes { 250 | if _, ok := tmp[includes[i]]; !ok { 251 | unsupported = append(unsupported, includes[i]) 252 | continue 253 | } 254 | cfg.InstanceInfo.Types = append(cfg.InstanceInfo.Types, includes[i]) 255 | } 256 | if len(unsupported) > 0 { 257 | level.Warn(c.logger). 258 | Log("msg", fmt.Sprintf("unsupported instance info collectors %s, implement by your own OR skip scraping instance info for those types", unsupported)) 259 | } 260 | } 261 | 262 | for _, res := range metas { 263 | if _, ok := cfg.Metrics[res.Namespace]; !ok { 264 | cfg.Metrics[res.Namespace] = make([]*config.Metric, 0) 265 | } 266 | cfg.Metrics[res.Namespace] = append(cfg.Metrics[res.Namespace], &config.Metric{ 267 | Name: res.MetricName, 268 | Period: res.Periods, 269 | Description: res.Description, 270 | Dimensions: strings.Split(res.Dimensions, ","), 271 | Unit: res.Unit, 272 | Measure: res.Statistics, 273 | }) 274 | } 275 | return cfg, nil 276 | } 277 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 2 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 3 | github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= 4 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 5 | github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= 6 | github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= 7 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= 8 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= 9 | github.com/aliyun/alibaba-cloud-sdk-go v1.62.807 h1:xFbDIciAT09Iv5gw8r5cSmMazenyo1eQSwtteTRO70s= 10 | github.com/aliyun/alibaba-cloud-sdk-go v1.62.807/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= 11 | github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= 12 | github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 13 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 14 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 15 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 16 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 17 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 18 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 19 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 24 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 25 | github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= 26 | github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 27 | github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= 28 | github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 29 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 30 | github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= 31 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 32 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 34 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 35 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 36 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 37 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 38 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 39 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 40 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 41 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 42 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 43 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 44 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 45 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 46 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 47 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 48 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 49 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 50 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 51 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 54 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 55 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 56 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 57 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 58 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= 59 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 60 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 61 | github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= 62 | github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= 63 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 64 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 65 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 66 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 67 | github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= 68 | github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 69 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 70 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 71 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 72 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 73 | github.com/prometheus/exporter-toolkit v0.11.0 h1:yNTsuZ0aNCNFQ3aFTD2uhPOvr4iD7fdBvKPAEGkNf+g= 74 | github.com/prometheus/exporter-toolkit v0.11.0/go.mod h1:BVnENhnNecpwoTLiABx7mrPB/OLRIgN74qlQbV+FK1Q= 75 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 76 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 77 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 78 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 79 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 80 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 81 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 82 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 83 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 84 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 85 | github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= 86 | github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 87 | github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= 88 | github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= 89 | github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= 90 | github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= 91 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 92 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 93 | go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= 94 | go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= 95 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 96 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 97 | golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= 98 | golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= 99 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 100 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 101 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 102 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 103 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 104 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 105 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 106 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 107 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 108 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 109 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 110 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 111 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 112 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 113 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 114 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 115 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 116 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 117 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 118 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 119 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 120 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 121 | golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= 122 | golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 123 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 124 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 125 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 126 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 127 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 128 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 129 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 130 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 131 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 132 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 133 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= 134 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 135 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 136 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 137 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 138 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 139 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 140 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 141 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 142 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 143 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 144 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 145 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 146 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 147 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 148 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 149 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 150 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 151 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 152 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 153 | -------------------------------------------------------------------------------- /example.yaml: -------------------------------------------------------------------------------- 1 | access_key: 2 | access_key_secret: 3 | region: cn-shenzhen 4 | metrics: 5 | acs_kvstore: 6 | - name: ConnectionUsage 7 | period: 60,300 8 | desc: 已用连接数百分比 9 | dimensions: 10 | - userId 11 | - instanceId 12 | unit: "%" 13 | measure: Average,Minimum,Maximum 14 | - name: CpuUsage 15 | period: 60,300 16 | desc: 已用CPU百分比 17 | dimensions: 18 | - userId 19 | - instanceId 20 | unit: "%" 21 | measure: Average,Minimum,Maximum 22 | - name: IntranetIn 23 | period: 60,300 24 | desc: 写入网络带宽 25 | dimensions: 26 | - userId 27 | - instanceId 28 | unit: B/s 29 | measure: Average,Minimum,Maximum 30 | - name: IntranetOut 31 | period: 60,300 32 | desc: 读取网络带宽 33 | dimensions: 34 | - userId 35 | - instanceId 36 | unit: B/s 37 | measure: Average,Minimum,Maximum 38 | - name: MemoryUsage 39 | period: 60,300 40 | desc: 已用容量百分比 41 | dimensions: 42 | - userId 43 | - instanceId 44 | unit: "%" 45 | measure: Average,Minimum,Maximum 46 | - name: ShardingAvgRt 47 | period: 60,300 48 | desc: 平均响应时间 49 | dimensions: 50 | - userId 51 | - instanceId 52 | - nodeId 53 | unit: us 54 | measure: Average,Maximum 55 | - name: ShardingBlockedClients 56 | period: 60,300 57 | desc: 执行block命令后暂时挂起的客户端数量 58 | dimensions: 59 | - userId 60 | - instanceId 61 | - nodeId 62 | unit: count 63 | measure: Average,Maximum 64 | - name: ShardingConnectionUsage 65 | period: 60,300 66 | desc: 连接数使用率 67 | dimensions: 68 | - userId 69 | - instanceId 70 | - nodeId 71 | unit: "%" 72 | measure: Average,Maximum 73 | - name: ShardingCpuUsage 74 | period: 60,300 75 | desc: CPU使用率 76 | dimensions: 77 | - userId 78 | - instanceId 79 | - nodeId 80 | unit: "%" 81 | measure: Average,Maximum 82 | - name: ShardingGetQps 83 | period: 60,300 84 | desc: 读QPS 85 | dimensions: 86 | - userId 87 | - instanceId 88 | - nodeId 89 | unit: count/s 90 | measure: Average,Maximum 91 | - name: ShardingHitRate 92 | period: 60,300 93 | desc: 命中率 94 | dimensions: 95 | - userId 96 | - instanceId 97 | - nodeId 98 | unit: "%" 99 | measure: Average,Maximum 100 | - name: ShardingKeys 101 | period: 60,300 102 | desc: 缓存内 Key 数量 103 | dimensions: 104 | - userId 105 | - instanceId 106 | - nodeId 107 | unit: count 108 | measure: Average,Maximum 109 | - name: ShardingPutQps 110 | period: 60,300 111 | desc: 写QPS 112 | dimensions: 113 | - userId 114 | - instanceId 115 | - nodeId 116 | unit: count/s 117 | measure: Average,Maximum 118 | - name: ShardingSlowlogCount 119 | period: 60,300 120 | desc: 慢查询数量 121 | dimensions: 122 | - userId 123 | - instanceId 124 | - nodeId 125 | unit: count 126 | measure: Average,Maximum 127 | - name: ShardingUsedConnection 128 | period: 60,300 129 | desc: 已用连接数 130 | dimensions: 131 | - userId 132 | - instanceId 133 | - nodeId 134 | unit: count 135 | measure: Average,Maximum 136 | - name: ShardingUsedMemoryLua 137 | period: 60,300 138 | desc: Lua脚本使用内存量 139 | dimensions: 140 | - userId 141 | - instanceId 142 | - nodeId 143 | unit: B 144 | measure: Average,Maximum 145 | - name: ShardingUsedQPS 146 | period: 60,300 147 | desc: 平均每秒访问次数 148 | dimensions: 149 | - userId 150 | - instanceId 151 | - nodeId 152 | unit: count 153 | measure: Average,Maximum 154 | - name: SplitrwProxyMaxRequestSize 155 | period: 60,300 156 | desc: Proxy单个请求最大字节数 157 | dimensions: 158 | - userId 159 | - instanceId 160 | - nodeId 161 | unit: B 162 | measure: Average,Maximum 163 | - name: SplitrwProxyMaxResponseSize 164 | period: 60,300 165 | desc: Proxy单个响应的最大字节数 166 | dimensions: 167 | - userId 168 | - instanceId 169 | - nodeId 170 | unit: B 171 | measure: Average,Maximum 172 | acs_rds_dashboard: 173 | - name: ConnectionUsage 174 | period: 60,300 175 | desc: 连接数使用率 176 | dimensions: 177 | - userId 178 | - instanceId 179 | unit: "%" 180 | measure: Average,Minimum,Maximum 181 | - name: CpuUsage 182 | period: 60,300 183 | desc: CPU使用率 184 | dimensions: 185 | - userId 186 | - instanceId 187 | unit: "%" 188 | measure: Average,Minimum,Maximum 189 | - name: DiskUsage 190 | period: 60,300 191 | desc: 磁盘使用率 192 | dimensions: 193 | - userId 194 | - instanceId 195 | unit: "%" 196 | measure: Average,Minimum,Maximum 197 | - name: IOPSUsage 198 | period: 60,300 199 | desc: IOPS使用率 200 | dimensions: 201 | - userId 202 | - instanceId 203 | unit: "%" 204 | measure: Average,Minimum,Maximum 205 | - name: MemoryUsage 206 | period: 60,300 207 | desc: 内存使用率 208 | dimensions: 209 | - userId 210 | - instanceId 211 | unit: "%" 212 | measure: Average,Minimum,Maximum 213 | - name: MySQL_ActiveSessions 214 | period: 60,300 215 | desc: MySQL_ActiveSessions 216 | dimensions: 217 | - userId 218 | - instanceId 219 | unit: count 220 | measure: Average,Minimum,Maximum 221 | - name: MySQL_DataIOBytesPS 222 | period: 60,300 223 | desc: MySQL读写吞吐量 224 | dimensions: 225 | - userId 226 | - instanceId 227 | unit: B/s 228 | measure: Average,Maximum,Minimum 229 | - name: MySQL_InnoDBDataRead 230 | period: 60,300 231 | desc: MySQL_InnoDB每秒读取数据量 232 | dimensions: 233 | - userId 234 | - instanceId 235 | unit: KiB 236 | measure: Average,Maximum,Minimum 237 | - name: MySQL_InnoDBDataWritten 238 | period: 60,300 239 | desc: MySQL_InnoDB每秒写入数据量 240 | dimensions: 241 | - userId 242 | - instanceId 243 | unit: KiB 244 | measure: Average,Maximum,Minimum 245 | - name: MySQL_InnoDBRowDelete 246 | period: 60,300 247 | desc: MySQL_InnoDB每秒删除行数 248 | dimensions: 249 | - userId 250 | - instanceId 251 | unit: count/s 252 | measure: Average,Maximum,Minimum 253 | - name: MySQL_MDLLockSession 254 | period: 60,300 255 | desc: MDL锁阻塞的连接数 256 | dimensions: 257 | - userId 258 | - instanceId 259 | unit: count 260 | measure: Average,Maximum,Minimum 261 | - name: MySQL_NetworkInNew 262 | period: 60,300 263 | desc: MySQL网络流入带宽 264 | dimensions: 265 | - userId 266 | - instanceId 267 | unit: bit/s 268 | measure: Average,Minimum,Maximum 269 | - name: MySQL_NetworkOutNew 270 | period: 60,300 271 | desc: MySQL网络流出带宽 272 | dimensions: 273 | - userId 274 | - instanceId 275 | unit: bit/s 276 | measure: Average,Minimum,Maximum 277 | - name: MySQL_OpenFiles 278 | period: 60,300 279 | desc: 文件打开数 280 | dimensions: 281 | - userId 282 | - instanceId 283 | unit: count 284 | measure: Average,Maximum,Minimum 285 | - name: MySQL_QPS 286 | period: 60,300 287 | desc: MySQL每秒查询量 288 | dimensions: 289 | - userId 290 | - instanceId 291 | unit: count/s 292 | measure: Average,Maximum,Minimum 293 | - name: MySQL_SlaveIORunning 294 | period: 60,300 295 | desc: 只读实例IO线程状态 296 | dimensions: 297 | - userId 298 | - instanceId 299 | unit: value 300 | measure: Average,Maximum,Minimum 301 | - name: MySQL_SlaveSQLRunning 302 | period: 60,300 303 | desc: 只读实例SQL线程状态 304 | dimensions: 305 | - userId 306 | - instanceId 307 | unit: value 308 | measure: Average,Maximum,Minimum 309 | - name: MySQL_SlowQueries 310 | period: 60,300 311 | desc: MySQL每秒慢查询量 312 | dimensions: 313 | - userId 314 | - instanceId 315 | unit: count/s 316 | measure: Average,Maximum,Minimum 317 | - name: MySQL_StandbySyncLag 318 | period: 60,300 319 | desc: 主实例备库同步延迟 320 | dimensions: 321 | - userId 322 | - instanceId 323 | unit: s 324 | measure: Average,Maximum,Minimum 325 | - name: MySQL_TPS 326 | period: 60,300 327 | desc: MySQL每秒事务数 328 | dimensions: 329 | - userId 330 | - instanceId 331 | unit: count/s 332 | measure: Average,Maximum,Minimum 333 | - name: MySQL_ThreadsConnected 334 | period: 60,300 335 | desc: MySQL_线程连接数 336 | dimensions: 337 | - userId 338 | - instanceId 339 | unit: count 340 | measure: Average,Maximum,Minimum 341 | - name: MySQL_ThreadsRunning 342 | period: 60,300 343 | desc: MySQL_活跃线程数 344 | dimensions: 345 | - userId 346 | - instanceId 347 | unit: count 348 | measure: Average,Maximum,Minimum 349 | - name: MySQL_TmpDiskSize 350 | period: 60,300 351 | desc: MySQL_临时磁盘使用量 352 | dimensions: 353 | - userId 354 | - instanceId 355 | unit: MiB 356 | measure: Average,Maximum,Minimum 357 | - name: MySQL_TotalSessions 358 | period: 60,300 359 | desc: 所有会话连接 360 | dimensions: 361 | - userId 362 | - instanceId 363 | unit: count 364 | measure: Average,Maximum,Minimum 365 | acs_slb_dashboard: 366 | - name: ActiveConnection 367 | period: 60,300 368 | desc: 监听每秒活跃连接数 369 | dimensions: 370 | - userId 371 | - instanceId 372 | - port 373 | - protocol 374 | unit: count 375 | measure: Average,Minimum,Maximum 376 | - name: DropConnection 377 | period: 60,300 378 | desc: 监听每秒丢失连接数 379 | dimensions: 380 | - userId 381 | - instanceId 382 | - port 383 | - protocol 384 | unit: count/s 385 | measure: Average,Minimum,Maximum 386 | - name: HeathyServerCount 387 | period: 60,300 388 | desc: 健康检查后端健康ECS实例个数 389 | dimensions: 390 | - userId 391 | - instanceId 392 | - port 393 | - vip 394 | unit: count 395 | measure: Average,Minimum,Maximum 396 | - name: InactiveConnection 397 | period: 60,300 398 | desc: 监听非活跃连接数 399 | dimensions: 400 | - userId 401 | - instanceId 402 | - port 403 | - protocol 404 | unit: count 405 | measure: Average,Minimum,Maximum 406 | - name: MaxConnection 407 | period: 60,300 408 | desc: 监听每秒最大连接数 409 | dimensions: 410 | - userId 411 | - instanceId 412 | - port 413 | - protocol 414 | unit: count/s 415 | measure: Maximum,Minimum,Average 416 | - name: Qps 417 | period: 60,300 418 | desc: 七层监听QPS 419 | dimensions: 420 | - userId 421 | - instanceId 422 | - port 423 | - protocol 424 | unit: count/s 425 | measure: Average,Maximum,Minimum 426 | - name: Rt 427 | period: 60,300 428 | desc: 七层监听RT 429 | dimensions: 430 | - userId 431 | - instanceId 432 | - port 433 | - protocol 434 | unit: ms 435 | measure: Average 436 | - name: StatusCode4xx 437 | period: 60,300 438 | desc: 七层监听每秒状态码4XX数量 439 | dimensions: 440 | - userId 441 | - instanceId 442 | - port 443 | - protocol 444 | unit: count/s 445 | measure: Average 446 | - name: StatusCode5xx 447 | period: 60,300 448 | desc: 七层监听每秒状态码5XX数量 449 | dimensions: 450 | - userId 451 | - instanceId 452 | - port 453 | - protocol 454 | unit: count/s 455 | measure: Average 456 | - name: DropTrafficTX 457 | period: 60,300 458 | desc: 监听每秒丢失出bit数 459 | dimensions: 460 | - userId 461 | - instanceId 462 | - port 463 | - protocol 464 | unit: bit/s 465 | measure: Average,Minimum,Maximum 466 | - name: DropTrafficRX 467 | period: 60,300 468 | desc: 监听每秒丢失入bit数 469 | dimensions: 470 | - userId 471 | - instanceId 472 | - port 473 | - protocol 474 | unit: bit/s 475 | measure: Average,Minimum,Maximum 476 | - name: TrafficRXNew 477 | period: 60,300 478 | desc: 监听流入带宽 479 | dimensions: 480 | - userId 481 | - instanceId 482 | - port 483 | - protocol 484 | unit: bit/s 485 | measure: Average,Minimum,Maximum 486 | - name: TrafficTXNew 487 | period: 60,300 488 | desc: 监听流出带宽 489 | dimensions: 490 | - userId 491 | - instanceId 492 | - port 493 | - protocol 494 | unit: bit/s 495 | measure: Average,Minimum,Maximum 496 | - name: UnhealthyServerCount 497 | period: 60,300 498 | desc: 健康检查后端异常ECS实例个数 499 | dimensions: 500 | - userId 501 | - instanceId 502 | - port 503 | - vip 504 | unit: count 505 | measure: Average,Minimum,Maximum 506 | acs_polardb: 507 | - name: cluster_active_sessions 508 | period: 60,300 509 | desc: 活跃连接数 510 | dimensions: 511 | - userId 512 | - clusterId 513 | - nodeId 514 | unit: count 515 | measure: Average 516 | - name: cluster_blktag_utilization 517 | period: 60,300 518 | desc: blktag使用率 519 | dimensions: 520 | - userId 521 | - clusterId 522 | unit: "%" 523 | measure: Average 524 | - name: cluster_com_delete 525 | period: 60,300 526 | desc: 每秒Delete语句执行次数 527 | dimensions: 528 | - userId 529 | - clusterId 530 | - nodeId 531 | unit: count/s 532 | measure: Average,Maximum,Minimum 533 | - name: cluster_com_delete_multi 534 | period: 60,300 535 | desc: 每秒Multi-Delete语句执行次数 536 | dimensions: 537 | - userId 538 | - clusterId 539 | - nodeId 540 | unit: count/s 541 | measure: Average,Maximum,Minimum 542 | - name: cluster_com_insert 543 | period: 60,300 544 | desc: 每秒Insert语句执行次数 545 | dimensions: 546 | - userId 547 | - clusterId 548 | - nodeId 549 | unit: count/s 550 | measure: Average,Maximum,Minimum 551 | - name: cluster_com_insert_select 552 | period: 60,300 553 | desc: 每秒Insert-Select语句执行次数 554 | dimensions: 555 | - userId 556 | - clusterId 557 | - nodeId 558 | unit: count/s 559 | measure: Average,Maximum,Minimum 560 | - name: cluster_com_replace 561 | period: 60,300 562 | desc: 每秒Replace语句执行次数 563 | dimensions: 564 | - userId 565 | - clusterId 566 | - nodeId 567 | unit: count/s 568 | measure: Average,Maximum,Minimum 569 | - name: cluster_com_replace_select 570 | period: 60,300 571 | desc: 每秒Replace-Select语句执行次数 572 | dimensions: 573 | - userId 574 | - clusterId 575 | - nodeId 576 | unit: count/s 577 | measure: Average,Maximum,Minimum 578 | - name: cluster_com_select 579 | period: 60,300 580 | desc: 每秒Select语句执行次数 581 | dimensions: 582 | - userId 583 | - clusterId 584 | - nodeId 585 | unit: count/s 586 | measure: Average,Maximum,Minimum 587 | - name: cluster_com_update 588 | period: 60,300 589 | desc: 每秒Update语句执行次数 590 | dimensions: 591 | - userId 592 | - clusterId 593 | - nodeId 594 | unit: count/s 595 | measure: Average,Maximum,Minimum 596 | - name: cluster_com_update_multi 597 | period: 60,300 598 | desc: 每秒Multi-Update语句执行次数 599 | dimensions: 600 | - userId 601 | - clusterId 602 | - nodeId 603 | unit: count/s 604 | measure: Average,Maximum,Minimum 605 | - name: cluster_connection_utilization 606 | period: 60,300 607 | desc: 连接数使用率 608 | dimensions: 609 | - userId 610 | - clusterId 611 | - nodeId 612 | unit: "%" 613 | measure: Average,Maximum,Minimum 614 | - name: cluster_cpu_utilization 615 | period: 60,300 616 | desc: CPU使用率 617 | dimensions: 618 | - userId 619 | - clusterId 620 | - nodeId 621 | unit: "%" 622 | measure: Average 623 | - name: cluster_data_io 624 | period: 60,300 625 | desc: 每秒存储引擎IO吞吐量 626 | dimensions: 627 | - userId 628 | - clusterId 629 | - nodeId 630 | unit: KiB 631 | measure: Average 632 | - name: cluster_data_iops 633 | period: 60,300 634 | desc: 每秒存储引擎IO次数 635 | dimensions: 636 | - userId 637 | - clusterId 638 | - nodeId 639 | unit: count/s 640 | measure: Average 641 | - name: cluster_direntry_utilization 642 | period: 60,300 643 | desc: direntry使用率 644 | dimensions: 645 | - userId 646 | - clusterId 647 | unit: "%" 648 | measure: Average 649 | - name: cluster_disk_utilization 650 | period: 60,300 651 | desc: 磁盘使用率 652 | dimensions: 653 | - userId 654 | - clusterId 655 | unit: "%" 656 | measure: Average 657 | - name: cluster_innodb_buffer_dirty_ratio 658 | period: 60,300 659 | desc: 缓冲池脏块率 660 | dimensions: 661 | - userId 662 | - clusterId 663 | - nodeId 664 | unit: "%" 665 | measure: Average,Maximum,Minimum 666 | - name: cluster_innodb_buffer_read_hit_rate 667 | period: 60,300 668 | desc: 缓冲池的读命中率 669 | dimensions: 670 | - userId 671 | - clusterId 672 | - nodeId 673 | unit: "%" 674 | measure: Average,Maximum,Minimum 675 | - name: cluster_inode_utilization 676 | period: 60,300 677 | desc: inode使用率 678 | dimensions: 679 | - userId 680 | - clusterId 681 | unit: "%" 682 | measure: Average 683 | - name: cluster_input_traffic 684 | period: 60,300 685 | desc: 每秒网络输入流量 686 | dimensions: 687 | - userId 688 | - clusterId 689 | - nodeId 690 | unit: KiB/s 691 | measure: Average,Maximum,Minimum 692 | - name: cluster_iops 693 | period: 60,300 694 | desc: 每秒IO次数 695 | dimensions: 696 | - userId 697 | - clusterId 698 | - nodeId 699 | unit: count/s 700 | measure: Average 701 | - name: cluster_iops_usage 702 | period: 60,300 703 | desc: IOPS使用率 704 | dimensions: 705 | - userId 706 | - clusterId 707 | - nodeId 708 | unit: "%" 709 | measure: Average,Maximum,Minimum 710 | - name: cluster_mdl_lock_session 711 | period: 60,300 712 | desc: MDL锁阻塞的连接数 713 | dimensions: 714 | - userId 715 | - clusterId 716 | - nodeId 717 | unit: count 718 | measure: Average,Maximum,Minimum 719 | - name: cluster_mem_hit_ratio 720 | period: 60,300 721 | desc: 内存命中率 722 | dimensions: 723 | - userId 724 | - clusterId 725 | - nodeId 726 | unit: "%" 727 | measure: Average 728 | - name: cluster_memory_utilization 729 | period: 60,300 730 | desc: 内存使用率 731 | dimensions: 732 | - userId 733 | - clusterId 734 | - nodeId 735 | unit: "%" 736 | measure: Average 737 | - name: cluster_mps 738 | period: 60,300 739 | desc: 每秒数据操作数 740 | dimensions: 741 | - userId 742 | - clusterId 743 | - instanceId 744 | unit: count/s 745 | measure: Average,Maximum,Minimum 746 | - name: cluster_other_lock_session 747 | period: 60,300 748 | desc: 其它锁阻塞的连接数 749 | dimensions: 750 | - userId 751 | - clusterId 752 | - nodeId 753 | unit: count 754 | measure: Average,Maximum,Minimum 755 | - name: cluster_output_traffic 756 | period: 60,300 757 | desc: 每秒网络输出流量 758 | dimensions: 759 | - userId 760 | - clusterId 761 | - nodeId 762 | unit: KiB/s 763 | measure: Average,Maximum,Minimum 764 | - name: cluster_pcu_by_cpu_cores 765 | period: 60,300 766 | desc: PolarDB Serverless 集群进行资源弹性的管理单位,单次弹性的最小 PCU 单位为0.5 PCU 767 | dimensions: 768 | - userId 769 | - clusterId 770 | - nodeId 771 | unit: core 772 | measure: Average,Maximum,Minimum 773 | - name: cluster_prepared_stmt_count 774 | period: 60,300 775 | desc: Prepared Statement 数量 776 | dimensions: 777 | - userId 778 | - clusterId 779 | unit: count 780 | measure: Average,Maximum,Minimum 781 | - name: cluster_prepared_stmt_utilization 782 | period: 60,300 783 | desc: Prepared Statement 使用率 784 | dimensions: 785 | - userId 786 | - clusterId 787 | unit: "%" 788 | measure: Average,Maximum,Minimum 789 | - name: cluster_qps 790 | period: 60,300 791 | desc: 每秒查询数量 792 | dimensions: 793 | - userId 794 | - clusterId 795 | - nodeId 796 | unit: count 797 | measure: Average 798 | - name: cluster_redo_write_rate 799 | period: 60,300 800 | desc: redo日志写入速率 801 | dimensions: 802 | - userId 803 | - clusterId 804 | - nodeId 805 | unit: B/s 806 | measure: Average 807 | - name: cluster_replica_lag 808 | period: 60,300 809 | desc: 只读节点复制延迟 810 | dimensions: 811 | - userId 812 | - clusterId 813 | - instanceId 814 | unit: s 815 | measure: Average,Maximum,Minimum 816 | - name: cluster_slow_queries_ps 817 | period: 60,300 818 | desc: 每秒慢查询数量 819 | dimensions: 820 | - userId 821 | - clusterId 822 | - nodeId 823 | unit: count/s 824 | measure: Average 825 | - name: cluster_total_session 826 | period: 60,300 827 | desc: 当前总连接数 828 | dimensions: 829 | - userId 830 | - clusterId 831 | - nodeId 832 | unit: count 833 | measure: Average,Maximum,Minimum 834 | - name: cluster_tps 835 | period: 60,300 836 | desc: 每秒事务数 837 | dimensions: 838 | - userId 839 | - clusterId 840 | - nodeId 841 | unit: count/s 842 | measure: Average 843 | instance_info: 844 | types: 845 | - acs_kvstore 846 | - acs_rds_dashboard 847 | - acs_slb_dashboard 848 | - acs_polardb 849 | regions: 850 | - ap-southeast-1 851 | - cn-shenzhen 852 | --------------------------------------------------------------------------------