-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from yseto/fix-some
some fix
- Loading branch information
Showing
29 changed files
with
1,791 additions
and
588 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
--- | ||
name: test | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
tags: | ||
- v* | ||
pull_request: | ||
env: | ||
DEBIAN_FRONTEND: noninteractive | ||
jobs: | ||
lint: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: golangci/golangci-lint-action@v3 | ||
test: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/setup-go@v3 | ||
with: | ||
go-version: "1.19.x" | ||
- uses: actions/checkout@v3 | ||
- uses: actions/cache@v3 | ||
with: | ||
path: ~/go/pkg/mod | ||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | ||
restore-keys: | | ||
${{ runner.os }}-go- | ||
- run: make deps-test test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
config.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,12 @@ | |
|
||
all: build | ||
|
||
deps: | ||
deps-lint: | ||
go install github.com/golangci/golangci-lint/cmd/[email protected] | ||
|
||
deps-test: | ||
go install github.com/tenntenn/testtime/cmd/testtime@latest | ||
|
||
fmt: | ||
go fmt ./... | ||
|
||
|
@@ -13,3 +16,7 @@ lint: | |
|
||
build: | ||
go build | ||
|
||
test: | ||
go test -overlay=`testtime` -v ./... | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,75 @@ | ||
# switch-traffic-to-mackerel | ||
|
||
## usage | ||
**これは趣味プロダクトです** | ||
|
||
ネットワークスイッチなどに対してSNMPで問い合わせを行い、インターフェイスの通信量などの統計情報を取得するプログラムです。 | ||
|
||
レポジトリ名にある通り、[mackerel.io](https://ja.mackerel.io/)に情報を送ることを主な目的としています。 | ||
|
||
## 特徴 | ||
|
||
- GETBULKを用いて値を取得するので、比較的速く動作します。 | ||
- 取り込むインターフェイス名を正規表現で指定することができるので、取り込みたくないインターフェイスを除外できます。 | ||
- mackerelに対して、通信量をシステムメトリックとして投稿するため、このプログラムが異常終了した場合など送信が失敗している状態に、死活監視で気づくことができます。 | ||
- mackerelとの通信が途絶えた場合でもプログラム内部でキャッシュし、通信が再開できたときに一斉に送信します。 | ||
|
||
## 使い方 | ||
|
||
1. config.yaml.sample を config.yaml という名前でコピーします | ||
2. config.yaml を開き加工します | ||
3. `switch-traffic-to-mackerel -config config.yaml` で起動する | ||
|
||
## 設定ファイルの内容 | ||
|
||
```yaml | ||
community: public # (必須)取得する対象のスイッチなどの SNMP コミュニティ名を設定する | ||
target: 192.2.0.1 # (必須)取得する対象のスイッチなどの IPアドレスを設定する | ||
interface: # (オプション)取り込むインターフェイスをインターフェイス名を使って絞り込むことができます。includeとexcludeはそれぞれ排他です。 | ||
include: "" # 取得時に取り込みたいインターフェイス名を正規表現で指定します | ||
exclude: "" # 取得時に取り込みたくないインターフェイス名を正規表現で指定します | ||
mibs: # (オプション)取り込みたい情報を設定できます。無指定時は、以下に示されるMIBについての情報が取り込まれます | ||
- ifHCInOctets | ||
- ifHCOutOctets | ||
- ifInDiscards | ||
- ifOutDiscards | ||
- ifInErrors | ||
- ifOutErrors | ||
# 機器によっては ifHCInOctets、ifHCOutOctets への対応ができない場合があります。その場合は、以下を明示的に指定する必要があります | ||
# - ifInOctets | ||
# - ifOutOctets | ||
debug: false # (オプション) true時、デバッグ表示を有効にします。取り込むインターフェイス名およびその値を表示します | ||
dry-run: false # (オプション) true時、mackerel への送信を抑制します。mackerel についての情報が設定ファイルに含まれてない場合は、強制的に true となります。 | ||
skip-linkdown: false # (オプション) downしているインターフェイスについては取り込みをスキップするオプションです | ||
mackerel: # (オプション)Mackerel に送信する時のパラメータ | ||
name: "" # (オプション)Mackerel に登録するホスト名 | ||
x-api-key: xxxxx # (必須) Mackerel の APIキー | ||
host-id: xxxxx # (オプション) Mackerel でのホストID、無指定時の場合、プログラム内で自動的に取得し、設定ファイルを更新します。 | ||
``` | ||
|
||
## v0.0.1 からの移行 | ||
|
||
v0.0.1 までは設定ファイルを基本的に使用していませんでした。そのため設定ファイルを作成する必要があります。 | ||
|
||
以下のようなコマンドラインで起動させていた場合は、後述するようなYAMLになります | ||
|
||
``` | ||
export MACKEREL_API_KEY=xxxx | ||
./switch-traffic-to-mackerel -target 192.0.2.1 -mibs ifHCInOctets,ifHCOutOctets -include-interface 'ge-0/0/\d+$|ae0$' -skip-down-link-state -name sw1 | ||
``` | ||
|
||
```yaml | ||
community: public | ||
target: 192.0.2.1 | ||
mibs: | ||
- ifHCInOctets | ||
- ifHCOutOctets | ||
interface: | ||
include: ge-0/0/\d+$|ae0$ | ||
skip-linkdown: true | ||
mackerel: | ||
host-id: <192.0.2.1.id.txt というような ${target}.id.txt というファイルにホストIDが記録されているので転記してください> | ||
x-api-key: xxxx # 環境変数を読まなくなりましたので、直接記述してください | ||
name: sw1 | ||
``` | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package collector | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/yseto/switch-traffic-to-mackerel/config" | ||
"github.com/yseto/switch-traffic-to-mackerel/mib" | ||
"github.com/yseto/switch-traffic-to-mackerel/snmp" | ||
) | ||
|
||
type snmpClientImpl interface { | ||
BulkWalk(oid string, length uint64) (map[uint64]uint64, error) | ||
BulkWalkGetInterfaceName(length uint64) (map[uint64]string, error) | ||
BulkWalkGetInterfaceState(length uint64) (map[uint64]bool, error) | ||
Close() error | ||
GetInterfaceNumber() (uint64, error) | ||
} | ||
|
||
func Do(ctx context.Context, c *config.Config) ([]MetricsDutum, error) { | ||
snmpClient, err := snmp.Init(ctx, c.Target, c.Community) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer snmpClient.Close() | ||
return do(ctx, snmpClient, c) | ||
} | ||
|
||
func do(ctx context.Context, snmpClient snmpClientImpl, c *config.Config) ([]MetricsDutum, error) { | ||
ifNumber, err := snmpClient.GetInterfaceNumber() | ||
if err != nil { | ||
return nil, err | ||
} | ||
ifDescr, err := snmpClient.BulkWalkGetInterfaceName(ifNumber) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var ifOperStatus map[uint64]bool | ||
if c.SkipDownLinkState { | ||
ifOperStatus, err = snmpClient.BulkWalkGetInterfaceState(ifNumber) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
metrics := make([]MetricsDutum, 0) | ||
|
||
for _, mibName := range c.MIBs { | ||
values, err := snmpClient.BulkWalk(mib.Oidmapping[mibName], ifNumber) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for ifIndex, value := range values { | ||
ifName := ifDescr[ifIndex] | ||
if c.IncludeRegexp != nil && !c.IncludeRegexp.MatchString(ifName) { | ||
continue | ||
} | ||
|
||
if c.ExcludeRegexp != nil && c.ExcludeRegexp.MatchString(ifName) { | ||
continue | ||
} | ||
|
||
// skip when down(2) | ||
if c.SkipDownLinkState && !ifOperStatus[ifIndex] { | ||
continue | ||
} | ||
|
||
metrics = append(metrics, MetricsDutum{IfIndex: ifIndex, Mib: mibName, IfName: ifName, Value: value}) | ||
} | ||
} | ||
if c.Debug { | ||
debugPrint(metrics) | ||
} | ||
return metrics, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package collector | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"regexp" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/google/go-cmp/cmp/cmpopts" | ||
|
||
"github.com/yseto/switch-traffic-to-mackerel/config" | ||
) | ||
|
||
type mockSnmpClient struct { | ||
} | ||
|
||
var errInvalid = errors.New("invalid error") | ||
|
||
func (m *mockSnmpClient) BulkWalk(oid string, length uint64) (map[uint64]uint64, error) { | ||
switch oid { | ||
case "1.3.6.1.2.1.31.1.1.1.6": | ||
return map[uint64]uint64{ | ||
1: 60, | ||
2: 60, | ||
3: 60, | ||
4: 60, | ||
}, nil | ||
case "1.3.6.1.2.1.31.1.1.1.10": | ||
return map[uint64]uint64{ | ||
1: 120, | ||
2: 120, | ||
3: 120, | ||
4: 120, | ||
}, nil | ||
default: | ||
return nil, errInvalid | ||
} | ||
} | ||
func (m *mockSnmpClient) BulkWalkGetInterfaceName(length uint64) (map[uint64]string, error) { | ||
return map[uint64]string{ | ||
1: "lo0", | ||
2: "eth0", | ||
3: "eth1", | ||
4: "eth2", | ||
}, nil | ||
} | ||
func (m *mockSnmpClient) BulkWalkGetInterfaceState(length uint64) (map[uint64]bool, error) { | ||
return map[uint64]bool{ | ||
1: true, | ||
2: false, | ||
3: true, | ||
4: true, | ||
}, nil | ||
} | ||
func (m *mockSnmpClient) Close() error { | ||
return nil | ||
} | ||
func (m *mockSnmpClient) GetInterfaceNumber() (uint64, error) { | ||
return 4, nil | ||
} | ||
|
||
func TestDo(t *testing.T) { | ||
ctx := context.Background() | ||
|
||
t.Run("non skip", func(t *testing.T) { | ||
c := &config.Config{ | ||
MIBs: []string{"ifHCInOctets", "ifHCOutOctets"}, | ||
} | ||
actual, err := do(ctx, &mockSnmpClient{}, c) | ||
if err != nil { | ||
t.Error("invalid raised error") | ||
} | ||
expected := []MetricsDutum{ | ||
{IfIndex: 1, Mib: "ifHCInOctets", IfName: "lo0", Value: 60}, | ||
{IfIndex: 2, Mib: "ifHCInOctets", IfName: "eth0", Value: 60}, | ||
{IfIndex: 3, Mib: "ifHCInOctets", IfName: "eth1", Value: 60}, | ||
{IfIndex: 4, Mib: "ifHCInOctets", IfName: "eth2", Value: 60}, | ||
{IfIndex: 1, Mib: "ifHCOutOctets", IfName: "lo0", Value: 120}, | ||
{IfIndex: 2, Mib: "ifHCOutOctets", IfName: "eth0", Value: 120}, | ||
{IfIndex: 3, Mib: "ifHCOutOctets", IfName: "eth1", Value: 120}, | ||
{IfIndex: 4, Mib: "ifHCOutOctets", IfName: "eth2", Value: 120}, | ||
} | ||
if d := cmp.Diff( | ||
actual, | ||
expected, | ||
cmpopts.SortSlices(func(i, j MetricsDutum) bool { return i.String() < j.String() }), | ||
); d != "" { | ||
t.Errorf("invalid result %s", d) | ||
} | ||
}) | ||
|
||
t.Run("skip include", func(t *testing.T) { | ||
c := &config.Config{ | ||
MIBs: []string{"ifHCInOctets", "ifHCOutOctets"}, | ||
IncludeRegexp: regexp.MustCompile("lo?"), | ||
} | ||
actual, err := do(ctx, &mockSnmpClient{}, c) | ||
if err != nil { | ||
t.Error("invalid raised error") | ||
} | ||
expected := []MetricsDutum{ | ||
{IfIndex: 1, Mib: "ifHCInOctets", IfName: "lo0", Value: 60}, | ||
{IfIndex: 1, Mib: "ifHCOutOctets", IfName: "lo0", Value: 120}, | ||
} | ||
if d := cmp.Diff( | ||
actual, | ||
expected, | ||
cmpopts.SortSlices(func(i, j MetricsDutum) bool { return i.String() < j.String() }), | ||
); d != "" { | ||
t.Errorf("invalid result %s", d) | ||
} | ||
}) | ||
t.Run("skip exclude", func(t *testing.T) { | ||
c := &config.Config{ | ||
MIBs: []string{"ifHCInOctets", "ifHCOutOctets"}, | ||
ExcludeRegexp: regexp.MustCompile("0$"), | ||
} | ||
actual, err := do(ctx, &mockSnmpClient{}, c) | ||
if err != nil { | ||
t.Error("invalid raised error") | ||
} | ||
expected := []MetricsDutum{ | ||
{IfIndex: 3, Mib: "ifHCInOctets", IfName: "eth1", Value: 60}, | ||
{IfIndex: 4, Mib: "ifHCInOctets", IfName: "eth2", Value: 60}, | ||
{IfIndex: 3, Mib: "ifHCOutOctets", IfName: "eth1", Value: 120}, | ||
{IfIndex: 4, Mib: "ifHCOutOctets", IfName: "eth2", Value: 120}, | ||
} | ||
if d := cmp.Diff( | ||
actual, | ||
expected, | ||
cmpopts.SortSlices(func(i, j MetricsDutum) bool { return i.String() < j.String() }), | ||
); d != "" { | ||
t.Errorf("invalid result %s", d) | ||
} | ||
}) | ||
|
||
t.Run("non skip", func(t *testing.T) { | ||
c := &config.Config{ | ||
MIBs: []string{"ifHCInOctets", "ifHCOutOctets"}, | ||
SkipDownLinkState: true, | ||
} | ||
actual, err := do(ctx, &mockSnmpClient{}, c) | ||
if err != nil { | ||
t.Error("invalid raised error") | ||
} | ||
expected := []MetricsDutum{ | ||
{IfIndex: 1, Mib: "ifHCInOctets", IfName: "lo0", Value: 60}, | ||
{IfIndex: 3, Mib: "ifHCInOctets", IfName: "eth1", Value: 60}, | ||
{IfIndex: 4, Mib: "ifHCInOctets", IfName: "eth2", Value: 60}, | ||
{IfIndex: 1, Mib: "ifHCOutOctets", IfName: "lo0", Value: 120}, | ||
{IfIndex: 3, Mib: "ifHCOutOctets", IfName: "eth1", Value: 120}, | ||
{IfIndex: 4, Mib: "ifHCOutOctets", IfName: "eth2", Value: 120}, | ||
} | ||
if d := cmp.Diff( | ||
actual, | ||
expected, | ||
cmpopts.SortSlices(func(i, j MetricsDutum) bool { return i.String() < j.String() }), | ||
); d != "" { | ||
t.Errorf("invalid result %s", d) | ||
} | ||
}) | ||
|
||
} |
Oops, something went wrong.