Skip to content

Commit

Permalink
Merge pull request #16 from tukaelu/support-service-metrics
Browse files Browse the repository at this point in the history
Support service metrics
  • Loading branch information
tukaelu authored Oct 20, 2023
2 parents 55cf46b + 47209f7 commit 8beda80
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 195 deletions.
24 changes: 22 additions & 2 deletions README-ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
## 概要

Mackerelの任意のホストの指定した期間に投稿されたメトリックをCSVファイルに出力する非公式なコマンドラインツールです。
初版ではホストメトリックの取得にのみ対応しています
ホストメトリックとサービスメトリックの取得に対応しています

なお、このツールは大量のAPIリクエストを行うことがあります。多重実行はサービスに負荷をかける事も考えられるのでお控えください。

Expand All @@ -23,6 +23,7 @@ brew install tukaelu/tap/sabadashi

## 使用方法

### ホストメトリック
```
NAME:
sabadashi host - Retrieves host metrics
Expand All @@ -39,6 +40,24 @@ OPTIONS:
--help, -h ヘルプを表示する
```

### サービスメトリック
```
NAME:
sabadashi service - Retrieves service metrics
USAGE:
sabadashi service [command options] [arguments...]
OPTIONS:
--name value, -n value メトリックを取得するサービス名を指定
--from value YYYYMMDD形式でメトリック取得を開始する日付を指定 (例: 20230101)
--to value YYYYMMDD形式でメトリック取得を終了する日付を指定 (例: 20231231)
--granularity value, -g value 取得するメトリックの粒度を 1m, 5m, 10m, 1h, 2h, 4h, 1d から指定 (デフォルト: 1m)
--with-friendly-date-format, -f フラグを有効にするとCSVの行頭に読みやすい日付のカラムを追加 (デフォルト: 追加しない)
--with-external-monitors, -e フラグを有効にすると外形監視で計測したメトリックも含める (デフォルト: 追加しない)
--help, -h ヘルプを表示する
```

メトリックを取得するホストのID、YYYYMMDD形式の取得の開始日と終了日を指定します。
コマンドオプションのfromに指定された`YYYY/MM/DD 00:00:00`から、toに指定された`YYYY/MM/DD 23:59:59`までに投稿されたメトリックを取得します。
有効な期間の範囲は460日間です。
Expand All @@ -59,7 +78,8 @@ sabadashi host -apikey <your api key> -host <your host id> -from <YYYYMMDD> -to

- 非公式なプラグインのため、ご質問はIssueやSNSなどでお願いします。
- 前述の通り、複数のホストのメトリクスを同時に取得する行為はサービスに負荷をかけることがあるためお控えください。
- ホストメトリックは[メトリック名の一覧API](https://mackerel.io/ja/api-docs/entry/hosts#metric-names)を元にメトリックを取得しています。
- ホストメトリックは[ホストのメトリック名の一覧API](https://mackerel.io/ja/api-docs/entry/hosts#metric-names)を元にメトリックを取得しています。
- サービスメトリックは[サービスのメトリック名の一覧API](https://mackerel.io/ja/api-docs/entry/services#metric-names)を元にして、外形監視を対象にするオプションが有効な場合に限り、[監視ルールの一覧](https://mackerel.io/ja/api-docs/entry/monitors#list)から外形監視で計測されたメトリックを追加してメトリックを取得しています。
- 指定した期間中にメトリックが投稿されていない場合、その時間帯のデータはCSVには出力されず、空のファイルだけが作成されることもあります。

## ライセンス
Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
## Description

**sabadashi** is a CLI tool for retrieves metrics posted to Mackerel for a specified period of time.
The first release only supports the acquisition of host metrics.
Supports retrieving host metrics and service metrics.

I note that this section is very important, but this tool makes many API calls.
Therefore, please refrain from executing them concurrently.
Expand All @@ -26,6 +26,8 @@ Please download the appropriate Zip archive for your environment from the [relea

## Usage

### Host metrics

```
NAME:
sabadashi host - Retrieves host metrics
Expand All @@ -42,6 +44,24 @@ OPTIONS:
--help, -h show help
```

### Service metrics
```
NAME:
sabadashi service - Retrieves service metrics
USAGE:
sabadashi service [command options] [arguments...]
OPTIONS:
--name value, -n value Name of the service from which to retrieve metric
--from value Specify the date to start retrieving metrics in YYYYMMDD format. (e.g. 20230101)
--to value Specify the date to end retrieving metrics in YYYYMMDD format. (e.g. 20231231)
--granularity value, -g value Specify the granularity of metric data. Choose from 1m, 5m, 10m, 1h, 2h, 4h or 1d. (default: 1m)
--with-friendly-date-format, -f If this flag is enabled, an additional column with a friendly date format is output at the beginning of the CSV line. (default: false)
--with-external-monitors, -e If this flag is enabled, it also includes the metric measured in the external monitoring. (default: false)
--help, -h show help
```

Specify the ID of the host from which the metric is to be retrieved, and the start and end dates of the retrieval in the format YYYYYMMDD.
The tool will retrieve metrics posted from `YYYYY/MM/DD 00:00:00` specified in `from` to `YYYYY/MM/DD 23:59:59` specified in `to`. And the validity period is 460 days.

Expand All @@ -61,7 +81,8 @@ When the command is executed, a directory named by host ID and start/end date wi

- This plugin is unofficial. Please ask questions via Issue or SNS.
- As mentioned earlier, the concurrent act of retrieving metrics for multiple hosts should be avoided, as it puts a load on the service.
- The tool is based on the [List Metric Names API](https://mackerel.io/api-docs/entry/hosts#metric-names) to retrieve metrics.
- Host metrics are retrieved based on the [List Metric Names API](https://mackerel.io/api-docs/entry/hosts#metric-names) of host metrics.
- The service metrics are retrieved based on the [List Metric Names API](https://mackerel.io/api-docs/entry/services#metric-names) of service metrics and, only if the option to target external monitoring is enabled, additional metrics measured through external monitoring are added from the [List Monitor Configurations API](https://mackerel.io/api-docs/entry/monitors#list).
- If a metric has not been submitted during the specified time period, the data for that time period will not be output as rows in the CSV and may in some cases result in an empty file.

## License
Expand Down
2 changes: 1 addition & 1 deletion cmd/sabadashi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

"github.com/urfave/cli/v2"

"github.com/tukaelu/sabadashi/internal/subcommand"
"github.com/tukaelu/sabadashi/cmd/subcommand"
)

var version string
Expand Down
23 changes: 13 additions & 10 deletions internal/subcommand/host/cli.go → cmd/subcommand/cli_host.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package host
package subcommand

import (
"github.com/mackerelio/mackerel-client-go"
Expand Down Expand Up @@ -27,16 +27,18 @@ func NewHostSubcommand() *cli.Command {
}

cmd := &hostCommand{
client: mackerel.NewClient(ctx.String("apikey")),
host: ctx.String("host"),
from: from,
to: to,
granularity: ctx.String("granularity"),
friendly: ctx.Bool("with-friendly-date-format"),
rawFrom: ctx.String("from"),
rawTo: ctx.String("to"),
baseCommand: baseCommand{
client: mackerel.NewClient(ctx.String("apikey")),
from: from,
to: to,
granularity: ctx.String("granularity"),
withFriendly: ctx.Bool("with-friendly-date-format"),
rawFrom: ctx.String("from"),
rawTo: ctx.String("to"),
},
host: ctx.String("host"),
}
return run(ctx, cmd)
return doHost(ctx, cmd)
},
Flags: []cli.Flag{
&cli.StringFlag{
Expand Down Expand Up @@ -65,6 +67,7 @@ func NewHostSubcommand() *cli.Command {
Name: "granularity",
Aliases: []string{"g"},
Usage: "Specify the granularity of metric data. Choose from 1m, 5m, 10m, 1h, 2h, 4h or 1d.",
Value: "1m",
DefaultText: "1m",
Action: func(ctx *cli.Context, s string) error {
return validator.ValidateGranularity(s)
Expand Down
88 changes: 88 additions & 0 deletions cmd/subcommand/cli_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package subcommand

import (
"github.com/mackerelio/mackerel-client-go"
"github.com/urfave/cli/v2"

"github.com/tukaelu/sabadashi/internal/converter"
"github.com/tukaelu/sabadashi/internal/validator"
)

func NewServiceSubCommand() *cli.Command {
return &cli.Command{
Name: "service",
Usage: "Retrieves service metrics",
Action: func(ctx *cli.Context) error {

from := converter.ToStartDayOfUnixtime(ctx.String("from"))
to := converter.ToEndDayOfUnixtime(ctx.String("to"))

if err := validator.ValidateTheDateBeforeAndAfter(from, to); err != nil {
return err
}
if err := validator.ValidateMetricRetantionPeriod(from, to); err != nil {
return err
}

cmd := &serviceCommand{
baseCommand: baseCommand{
client: mackerel.NewClient(ctx.String("apikey")),
from: from,
to: to,
granularity: ctx.String("granularity"),
withFriendly: ctx.Bool("with-friendly-date-format"),
rawFrom: ctx.String("from"),
rawTo: ctx.String("to"),
},
name: ctx.String("name"),
withExternal: ctx.Bool("with-external-monitors"),
}

return doService(ctx, cmd)
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Usage: "Name of the service from which to retrieve metric",
Required: true,
},
&cli.StringFlag{
Name: "from",
Usage: "Specify the date to start retrieving metrics in YYYYMMDD format. (e.g. 20230101)",
Required: true,
Action: func(ctx *cli.Context, s string) error {
return validator.ValidateDateFormat(s)
},
},
&cli.StringFlag{
Name: "to",
Usage: "Specify the date to end retrieving metrics in YYYYMMDD format. (e.g. 20231231)",
Required: true,
Action: func(ctx *cli.Context, s string) error {
return validator.ValidateDateFormat(s)
},
},
&cli.StringFlag{
Name: "granularity",
Aliases: []string{"g"},
Usage: "Specify the granularity of metric data. Choose from 1m, 5m, 10m, 1h, 2h, 4h or 1d.",
Value: "1m",
DefaultText: "1m",
Action: func(ctx *cli.Context, s string) error {
return validator.ValidateGranularity(s)
},
},
&cli.BoolFlag{
Name: "with-friendly-date-format",
Aliases: []string{"f"},
Usage: "If this flag is enabled, an additional column with a friendly date format is output at the beginning of the CSV line.",
},
&cli.BoolFlag{
Name: "with-external-monitors",
Aliases: []string{"e"},
Usage: "If this flag is enabled, it also includes the metric measured in the external monitoring.",
},
},
}
}
105 changes: 105 additions & 0 deletions cmd/subcommand/cmd_host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package subcommand

import (
"context"
"fmt"
"os"
"sync"
"time"

"github.com/mackerelio/mackerel-client-go"
"github.com/schollz/progressbar/v3"
"github.com/urfave/cli/v2"

"github.com/tukaelu/sabadashi/internal/converter"
"github.com/tukaelu/sabadashi/internal/definition"
"github.com/tukaelu/sabadashi/internal/exporter"
"github.com/tukaelu/sabadashi/internal/fileutil"
"github.com/tukaelu/sabadashi/internal/retriever"
)

type hostCommand struct {
baseCommand
host string
}

func doHost(ctx *cli.Context, h *hostCommand) error {

exportDir, err := fileutil.CreateExportDir(h.host, h.rawFrom, h.rawTo)
if err != nil {
return err
}

interval := converter.GranularityToInterval(h.granularity)
attempts := (h.to-h.from)/interval + 1

metricNames, err := h.client.ListHostMetricNames(h.host)
if err != nil {
return err
}

progress := progressbar.Default(attempts * int64(len(metricNames)))

ch := make(chan exporter.Result, definition.CONCURRENT_FILE_OPERATION)
defer close(ch)

go func(ch chan exporter.Result) {
for {
select {
case res := <-ch:
if err := fileutil.WriteFile(exportDir, res.MetricName, "csv", res.Records, h.withFriendly); err != nil {
fmt.Printf("Failed to write CSV file. (reason: %s)\n", err)
return
}
case <-ctx.Done():
fmt.Println("Operation canceled.")
os.Exit(125)
}
}
}(ch)

from := h.from
to := int64(0)
wg := &sync.WaitGroup{}
for i := int64(0); i < attempts; i++ {
to = from + interval
if to > h.to {
to = h.to
}

for _, metricName := range metricNames {
wg.Add(1)
go func(ctx *context.Context, metricName string) {
defer wg.Done()

metrics, err := retriever.Retrieve(ctx, metricName, func() ([]mackerel.MetricValue, error) {
metricValues, err := h.client.FetchHostMetricValues(h.host, metricName, from, to)
if err != nil {
return nil, fmt.Errorf("[ERROR] failed to retrieve metrics (metric: %s, from: %d, to: %d) (reason: %s)", metricName, from, to, err)
}
return metricValues, nil
})

if err != nil {
// skip retrieve metrics...
fmt.Sprintln(err)
}

ch <- exporter.Result{
MetricName: metricName,
Records: metrics,
}
}(&ctx.Context, metricName)

time.Sleep(350 * time.Millisecond)

_ = progress.Add(1)
}
wg.Wait()

from += interval
_ = progress.Add(1)
}

return nil
}
Loading

0 comments on commit 8beda80

Please sign in to comment.