Skip to content

Commit 405cd5e

Browse files
committed
First commit
0 parents  commit 405cd5e

File tree

5 files changed

+375
-0
lines changed

5 files changed

+375
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
unitymetrics
2+
unitymetrics.exe
3+
*.tar.gz
4+
*.zip

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 Erwan Quélin
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
all:
2+
$(MAKE) deps
3+
$(MAKE) unitymetrics
4+
5+
deps:
6+
go get -u github.com/equelin/gounity
7+
go get -u github.com/sirupsen/logrus
8+
9+
unitymetrics:
10+
env GOOS=linux GOARCH=amd64 go build -v github.com/equelin/unitymetrics
11+
env GOOS=windows GOARCH=amd64 go build -v github.com/equelin/unitymetrics
12+
13+
go-install:
14+
go install
15+
16+
install: unitymetrics
17+
cp ./unitymetrics /usr/local/bin
18+

README.md

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# UNITYMETRICS
2+
3+
Unitymetrics is a tool written in Go for collecting metrics from a Dell EMC Unity array and translating them in InfluxDB's line protocol.
4+
5+
It can be use to send metrics in a InfluxDB database with the help of Telegraf.
6+
7+
## How to find the available metrics
8+
9+
In the Unity API, metrics are define by a path. For example, if you want to collect the remaining memory available on the storage processors, you'll have to use the path `sp.*.memory.summary.freeBytes`.
10+
11+
You can find a list of the metrics [here](https://gist.github.com/equelin/37486519972f8161c480f47ae5904390).
12+
13+
If you look at the different path, you will figure that some of them contains `*` or `+` characters.
14+
15+
When there is a `*` in the path, you can use the path as-is in your request, the `*` will be automatically replaced with all the possibilities. For example, if you want to use the path `sp.*.memory.summary.freeBytes`. The API will interpret it as if you were requesting the free memory for the SPA and the SPB. If you need this information only for one of the SPs, you can use the path `sp.spa.memory.summary.freeBytes`
16+
17+
When there is a `+` in the path, you have to replace it with the relevant item by yourself before requesting the API. For example, if you want to retrieve the CPU utilization of the SPA, you have to modify the path `kpi.sp.+.utilization` like this `kpi.sp.spa.utilization`
18+
19+
## How to install it
20+
### From prebuilt release
21+
22+
You can find prebuilt unitymetrics binaries on the [releases page](https://github.com/equelin/unitymetrics/releases).
23+
24+
Download and install a binary locally like this:
25+
26+
``` console
27+
% curl -L $URL_TO_BINARY | gunzip > /usr/local/bin/unitymetrics
28+
% chmod +x /usr/local/bin/unitymetrics
29+
```
30+
31+
### From source
32+
33+
To build unitymetrics from source, first install the [Go toolchain](https://golang.org/dl/).
34+
35+
Make sure to set the environment variable [GOPATH](https://github.com/golang/go/wiki/SettingGOPATH).
36+
37+
You can then download the latest unitymetrics source code from github using:
38+
39+
``` console
40+
% go get -u github.com/equelin/unitymetrics
41+
```
42+
43+
Make sure `$GOPATH/bin` is in your `PATH`.
44+
45+
You can build unitymetrics using:
46+
47+
``` console
48+
% cd $GOPATH/src/github.com/equelin/unitymetrics/
49+
% make
50+
```
51+
52+
## How to use it
53+
54+
See usage with:
55+
56+
```
57+
./unitymetrics -h
58+
```
59+
60+
#### Run a Dell Unity metrics collection with the default metrics and a sampling interval
61+
62+
```
63+
./unitymetrics -unity unity01.example.com -user admin -password AwesomePassword
64+
```
65+
66+
#### Run a Dell Unity metrics collection with the default metrics and sampling interval of 10 seconds
67+
68+
```
69+
./unitymetrics -unity unity01.example.com -user admin -password AwesomePassword -interval 10
70+
```
71+
72+
#### Run a Dell Unity metrics collection with specific metrics and sampling interval of 10 seconds
73+
74+
```
75+
./unitymetrics -unity unity01.example.com -user admin -password AwesomePassword -interval 10 -paths kpi.sp.spa.utilization,sp.*.cpu.summary.busyTicks,sp.*.cpu.uptime,sp.*.storage.pool.*.sizeFree,
76+
```
77+
78+
## Using unitymetrics with Telegraf
79+
80+
The `exec` input plugin of Telegraf executes the `commands` on every interval and parses metrics from their output in any one of the accepted [Input Data Formats](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md).
81+
82+
`unitymetrics` output the metrics in InfluxDB's line protocol. Telegraf will parse them and send them to the InfluxDB database.
83+
84+
> Don't forget to configure Telegraf to output data to InfluxDB !
85+
86+
Here is an example of a working telegraf's config file:
87+
88+
```Toml
89+
###############################################################################
90+
# INPUT PLUGINS #
91+
###############################################################################
92+
93+
[[inputs.exec]]
94+
# Shell/commands array
95+
# Full command line to executable with parameters, or a glob pattern to run all matching files.
96+
commands = ["unitymetrics -user admin -password AwesomePassword -unity unity01.okcomputer.lab -interval 10 -paths kpi.sp.spa.utilization,sp.*.cpu.summary.busyTicks,sp.*.cpu.uptime,sp.*.storage.pool.*.sizeFree,sp.*.storage.pool.*.sizeSubscribed,sp.*.storage.pool.*.sizeTotal,sp.*.storage.pool.*sizeUsed,sp.*.memory.summary.totalBytes,sp.*.memory.summary.totalUsedBytes"]
97+
98+
# Timeout for each command to complete.
99+
timeout = "20s"
100+
101+
# Data format to consume.
102+
# NOTE json only reads numerical measurements, strings and booleans are ignored.
103+
data_format = "influx"
104+
```
105+
106+
# Author
107+
**Erwan Quélin**
108+
- <https://github.com/equelin>
109+
- <https://twitter.com/erwanquelin>
110+
111+
# License
112+
113+
Copyright 2018 Erwan Quelin and the community.
114+
115+
Licensed under the MIT License.
116+

unitymetrics.go

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"strings"
8+
"time"
9+
10+
"github.com/equelin/gounity"
11+
"github.com/sirupsen/logrus"
12+
)
13+
14+
var log = logrus.New()
15+
var unityName string
16+
17+
// https://stackoverflow.com/questions/29366038/looping-iterate-over-the-second-level-nested-json-in-go-lang
18+
func parseMap(index int, pathPtr *string, measurementNamePtr *string, tagNames map[int]string, tagsMap map[string]string, valuesMap map[string]interface{}) {
19+
20+
for key, val := range valuesMap {
21+
22+
pathSplit := strings.Split(*pathPtr, ".")
23+
24+
switch concreteVal := val.(type) {
25+
case map[string]interface{}:
26+
27+
ok := false
28+
29+
for i, v := range pathSplit {
30+
if v == key {
31+
tagName := pathSplit[i-1]
32+
tagsMap[tagName] = key
33+
ok = true
34+
}
35+
}
36+
37+
if ok != true {
38+
tagsMap[tagNames[index]] = key
39+
index++
40+
}
41+
42+
parseMap(
43+
index,
44+
pathPtr,
45+
measurementNamePtr,
46+
tagNames,
47+
tagsMap,
48+
val.(map[string]interface{}),
49+
)
50+
51+
default:
52+
53+
if len(tagNames) != 0 {
54+
tagsMap[tagNames[index]] = key
55+
} else {
56+
for i, v := range pathSplit {
57+
if v == key {
58+
tagName := pathSplit[i-1]
59+
tagsMap[tagName] = key
60+
}
61+
}
62+
}
63+
64+
// Formating tags set
65+
// <tag_key>=<tag_value>,<tag_key>=<tag_value>
66+
var tags string
67+
68+
tags = fmt.Sprintf("unity=%s", unityName)
69+
for k, v := range tagsMap {
70+
tags = tags + fmt.Sprintf(",%s=%s", k, v)
71+
}
72+
73+
// Formating fied set
74+
// <field_key>=<field_value>
75+
var field string
76+
field = fmt.Sprintf("%s=%s", *pathPtr, concreteVal)
77+
78+
// Formating and printing the result using the InfluxDB's Line Protocol
79+
// https://docs.influxdata.com/influxdb/v1.5/write_protocols/line_protocol_tutorial/
80+
fmt.Printf("%s,%s %s %d\n", *measurementNamePtr, tags, field, time.Now().UnixNano())
81+
}
82+
}
83+
}
84+
85+
func main() {
86+
87+
// Set logs parameters
88+
log.Out = os.Stdout
89+
90+
userPtr := flag.String("user", "", "Username")
91+
passwordPtr := flag.String("password", "", "Password")
92+
unityPtr := flag.String("unity", "", "Unity IP or FQDN")
93+
intervalPtr := flag.Uint64("interval", 30, "Sampling interval")
94+
pathsPtr := flag.String("paths", "kpi.sp.spa.utilization,sp.*.cpu.summary.busyTicks", "Unity metrics paths")
95+
debugPtr := flag.Bool("debug", false, "Debug mode")
96+
97+
flag.Parse()
98+
99+
if *debugPtr == true {
100+
log.Level = logrus.DebugLevel
101+
} else {
102+
log.Level = logrus.ErrorLevel
103+
}
104+
105+
log.WithFields(logrus.Fields{
106+
"event": "flag",
107+
"key": "user",
108+
"value": *userPtr,
109+
}).Debug("Parsed flag user")
110+
111+
log.WithFields(logrus.Fields{
112+
"event": "flag",
113+
"key": "unity",
114+
"value": *unityPtr,
115+
}).Debug("Parsed flag unity")
116+
117+
log.WithFields(logrus.Fields{
118+
"event": "flag",
119+
"key": "interval",
120+
"value": *intervalPtr,
121+
}).Debug("Parsed flag interval")
122+
123+
log.WithFields(logrus.Fields{
124+
"event": "flag",
125+
"key": "paths",
126+
"value": *pathsPtr,
127+
}).Debug("Parsed flag paths")
128+
129+
// Start a new Unity session
130+
131+
log.WithFields(logrus.Fields{
132+
"event": "gounity.NewSession",
133+
"unity": *unityPtr,
134+
"engineering": "true",
135+
"user": *userPtr,
136+
}).Debug("Started new Unity session")
137+
138+
session, err := gounity.NewSession(*unityPtr, true, *userPtr, *passwordPtr)
139+
140+
if err != nil {
141+
log.Fatal(err)
142+
}
143+
144+
defer session.CloseSession()
145+
146+
// Get system informations
147+
System, err := session.GetbasicSystemInfo()
148+
if err != nil {
149+
log.Fatal(err)
150+
}
151+
152+
// Store the name of the Unity
153+
unityName = System.Entries[0].Content.Name
154+
155+
// metric paths
156+
paths := strings.Split(*pathsPtr, ",")
157+
158+
// converting metric interval into uint32
159+
var interval = uint32(*intervalPtr)
160+
161+
// Request a new metric query
162+
Metric, err := session.NewMetricRealTimeQuery(paths, interval)
163+
if err != nil {
164+
log.Fatal(err)
165+
}
166+
167+
// Waiting that the sampling of the metrics is done
168+
time.Sleep(time.Duration(Metric.Content.Interval) * time.Second)
169+
170+
// Get the results of the query
171+
Result, err := session.GetMetricRealTimeQueryResult(Metric.Content.ID)
172+
if err != nil {
173+
log.Fatal(err)
174+
}
175+
176+
// Parse the results
177+
for _, v := range Result.Entries {
178+
179+
valuesMap := v.Content.Values.(map[string]interface{})
180+
181+
tagsMap := make(map[string]string)
182+
tagNames := make(map[int]string)
183+
184+
path := v.Content.Path
185+
186+
pathSplit := strings.Split(path, ".")
187+
188+
var measurementName string
189+
if pathSplit[0] == "kpi" {
190+
measurementName = "kpi"
191+
} else {
192+
measurementName = pathSplit[2]
193+
}
194+
195+
j := 0
196+
for i, v := range pathSplit {
197+
if v == "*" {
198+
tagName := pathSplit[i-1]
199+
tagNames[j] = tagName
200+
j++
201+
}
202+
}
203+
204+
parseMap(
205+
0,
206+
&path,
207+
&measurementName,
208+
tagNames,
209+
tagsMap,
210+
valuesMap,
211+
)
212+
}
213+
214+
/* TODO: DELETE THE QUERY */
215+
216+
}

0 commit comments

Comments
 (0)