├── .gitignore ├── Makefile ├── LICENSE ├── README.md └── unitymetrics.go /.gitignore: -------------------------------------------------------------------------------- 1 | unitymetrics 2 | unitymetrics.exe 3 | telegraf.conf 4 | *.tar.gz 5 | *.zip 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 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 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UNITYMETRICS 2 | 3 | Unitymetrics is a tool written in Go for collecting usage and performance metrics from a Dell EMC Unity array and translating them in InfluxDB's line protocol. 4 | 5 | ![unitymetrics-min](https://user-images.githubusercontent.com/9823778/38147007-b9abfe82-3450-11e8-8590-87d3afb7e480.png) 6 | 7 | It can be useful to send metrics in a InfluxDB database with the help of Telegraf and then display metrics in grafana. 8 | 9 | ![screenshot-2018-3-30 grafana - unity](https://user-images.githubusercontent.com/9823778/38147174-60410d8c-3451-11e8-8320-016c38dbe705.png) 10 | 11 | ## How to find the available metrics 12 | 13 | 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`. 14 | 15 | You can find a list of the metrics [here](https://gist.github.com/equelin/37486519972f8161c480f47ae5904390). 16 | 17 | If you look at the different path, you will figure that some of them contains `*` or `+` characters. 18 | 19 | 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` 20 | 21 | When there is a `+` in the path, you can replace it with the relevant item by yourself before requesting the API or by a `*` for breaking the results by this item. For example, if you want to specifically retrieve the CPU utilization of the SPA, you have to modify the path `kpi.sp.+.utilization` like this `kpi.sp.spa.utilization`. 22 | 23 | ## How to install it 24 | 25 | ### From prebuilt release 26 | 27 | You can find prebuilt unitymetrics binaries on the [releases page](https://github.com/equelin/unitymetrics/releases). 28 | 29 | Download and install a binary locally like this: 30 | 31 | ``` console 32 | % curl -L $URL_TO_BINARY | gunzip > /usr/local/bin/unitymetrics 33 | % chmod +x /usr/local/bin/unitymetrics 34 | ``` 35 | 36 | ### From source 37 | 38 | To build unitymetrics from source, first install the [Go toolchain](https://golang.org/dl/). 39 | 40 | Make sure to set the environment variable [GOPATH](https://github.com/golang/go/wiki/SettingGOPATH). 41 | 42 | You can then download the latest unitymetrics source code from github using: 43 | 44 | ``` console 45 | % go get -u github.com/equelin/unitymetrics 46 | ``` 47 | 48 | Make sure `$GOPATH/bin` is in your `PATH`. 49 | 50 | You can build unitymetrics using: 51 | 52 | ``` console 53 | % cd $GOPATH/src/github.com/equelin/unitymetrics/ 54 | % make 55 | ``` 56 | 57 | ## How to use it 58 | 59 | See usage with: 60 | 61 | ```bash 62 | ./unitymetrics -h 63 | ``` 64 | 65 | ### Run a Dell EMC Unity metrics collection for an historical path 66 | 67 | ```bash 68 | ./unitymetrics -unity unity01.example.com -user admin -password AwesomePassword -histkpipaths kpi.sp.*.utilization 69 | ``` 70 | 71 | ### Run a Dell EMC Unity metrics collection with a real time metric and sampling interval of 10 seconds 72 | 73 | ```bash 74 | ./unitymetrics -unity unity01.example.com -user admin -password AwesomePassword -rtpaths sp.*.memory.summary.freeBytes -interval 10 75 | ``` 76 | 77 | ### Run a Dell EMC Unity metrics collection with multiple metrics and sampling interval of 10 seconds 78 | 79 | ```bash 80 | ./unitymetrics -unity unity01.example.com -user admin -password AwesomePassword -interval 10 -histkpipaths kpi.sp.*.utilization,kpi.lun.+.sp.+.rw.+.throughput,kpi.lun.*.sp.+.rw.+.throughput,kpi.lun.+.sp.+.responseTime,kpi.lun.*.sp.+.responseTime,kpi.lun.+.sp.+.queueLength,kpi.lun.*.sp.+.queueLength 81 | ``` 82 | 83 | ### Run a Dell EMC Unity metrics collection for collecting capacity statistics 84 | 85 | ```bash 86 | ./unitymetrics -unity unity01.example.com -user admin -password AwesomePassword -capacity 87 | ``` 88 | 89 | ## Using unitymetrics with Telegraf 90 | 91 | 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). 92 | 93 | `unitymetrics` output the metrics in InfluxDB's line protocol. Telegraf will parse them and send them to the InfluxDB database. 94 | 95 | > Don't forget to configure Telegraf to output data to InfluxDB ! 96 | 97 | Here is an example of a working telegraf's config file: 98 | 99 | ```Toml 100 | ############################################################################### 101 | # INPUT PLUGINS # 102 | ############################################################################### 103 | 104 | [[inputs.exec]] 105 | # Shell/commands array 106 | # Full command line to executable with parameters, or a glob pattern to run all matching files. 107 | commands = ["unitymetrics -user admin -password Mypassword -unity unity01.example.com -histkpipaths kpi.sp.*.utilization,kpi.lun.+.sp.+.rw.+.throughput,kpi.lun.*.sp.+.rw.+.throughput,kpi.lun.+.sp.+.responseTime,kpi.lun.*.sp.+.responseTime,kpi.lun.+.sp.+.queueLength,kpi.lun.*.sp.+.queueLength -capacity"] 108 | 109 | # Timeout for each command to complete. 110 | timeout = "60s" 111 | 112 | # Data format to consume. 113 | # NOTE json only reads numerical measurements, strings and booleans are ignored. 114 | data_format = "influx" 115 | 116 | interval = "60s" 117 | ``` 118 | 119 | If needed, you can specify more than one input plugin. It might be useful if you want to gather different statistics with different intervals or if you want to query different arrays. 120 | 121 | ```Toml 122 | ############################################################################### 123 | # INPUT PLUGINS # 124 | ############################################################################### 125 | 126 | [[inputs.exec]] 127 | # Shell/commands array 128 | # Full command line to executable with parameters, or a glob pattern to run all matching files. 129 | commands = ["unitymetrics -user admin -password Mypassword -unity unity01.example.com -histkpipaths kpi.sp.*.utilization,kpi.lun.+.sp.+.rw.+.throughput,kpi.lun.*.sp.+.rw.+.throughput,kpi.lun.+.sp.+.responseTime,kpi.lun.*.sp.+.responseTime,kpi.lun.+.sp.+.queueLength,kpi.lun.*.sp.+.queueLength -capacity"] 130 | 131 | # Timeout for each command to complete. 132 | timeout = "60s" 133 | 134 | # Data format to consume. 135 | # NOTE json only reads numerical measurements, strings and booleans are ignored. 136 | data_format = "influx" 137 | 138 | interval = "60s" 139 | 140 | [[inputs.exec]] 141 | # Shell/commands array 142 | # Full command line to executable with parameters, or a glob pattern to run all matching files. 143 | commands = ["unitymetrics -user admin -password Mypassword -unity unity01.example.com -interval 50 -rtpaths sp.*.memory.summary.freeBytes,sp.*.memory.summary.totalBytes,sp.*.memory.summary.totalUsedBytes,sp.*.cpu.uptime"] 144 | 145 | # Timeout for each command to complete. 146 | timeout = "60s" 147 | 148 | # Data format to consume. 149 | # NOTE json only reads numerical measurements, strings and booleans are ignored. 150 | data_format = "influx" 151 | 152 | interval = "60s" 153 | 154 | [[inputs.exec]] 155 | # Shell/commands array 156 | # Full command line to executable with parameters, or a glob pattern to run all matching files. 157 | commands = ["unitymetrics -user admin -password Mypassword -unity unity02.example.com -histkpipaths kpi.sp.*.utilization,kpi.lun.+.sp.+.rw.+.throughput,kpi.lun.*.sp.+.rw.+.throughput,kpi.lun.+.sp.+.responseTime,kpi.lun.*.sp.+.responseTime,kpi.lun.+.sp.+.queueLength,kpi.lun.*.sp.+.queueLength -capacity"] 158 | 159 | # Timeout for each command to complete. 160 | timeout = "60s" 161 | 162 | # Data format to consume. 163 | # NOTE json only reads numerical measurements, strings and booleans are ignored. 164 | data_format = "influx" 165 | 166 | interval = "60s" 167 | ``` 168 | 169 | # Author 170 | **Erwan Quélin** 171 | - 172 | - 173 | 174 | # License 175 | 176 | Copyright 2018 Erwan Quelin and the community. 177 | 178 | Licensed under the MIT License. 179 | 180 | -------------------------------------------------------------------------------- /unitymetrics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/equelin/gounity" 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | // Types 16 | type pool struct { 17 | ID string `json:"id"` 18 | Name string `json:"name"` 19 | SizeFree uint64 `json:"sizeFree"` 20 | SizeTotal uint64 `json:"sizeTotal"` 21 | SizeUsed uint64 `json:"sizeUsed"` 22 | SizeSubscribed uint64 `json:"sizeSubscribed"` 23 | } 24 | 25 | type storageresource struct { 26 | ID string `json:"id"` 27 | Name string `json:"name"` 28 | SizeAllocated uint64 `json:"sizeAllocated"` 29 | SizeTotal uint64 `json:"sizeTotal"` 30 | SizeUsed uint64 `json:"sizeUsed"` 31 | Type int `json:"type"` 32 | } 33 | 34 | // Variables 35 | var log = logrus.New() 36 | var unityName string 37 | var unityPools []pool 38 | var unityStorageResource []storageresource 39 | 40 | // 41 | func parseResult(timestamp time.Time, path string, valuesMap map[string]interface{}) { 42 | 43 | tagsMap := make(map[string]string) 44 | tagNames := make(map[int]string) 45 | 46 | pathSplit := strings.Split(path, ".") 47 | 48 | var measurementName string 49 | if pathSplit[0] == "kpi" { 50 | measurementName = fmt.Sprintf("kpi_%s", pathSplit[1]) 51 | } else { 52 | measurementName = pathSplit[2] 53 | } 54 | 55 | j := 0 56 | for i, v := range pathSplit { 57 | if v == "*" { 58 | tagName := pathSplit[i-1] 59 | tagNames[j] = tagName 60 | j++ 61 | } 62 | } 63 | 64 | parseMap( 65 | timestamp, 66 | 0, 67 | &path, 68 | &measurementName, 69 | tagNames, 70 | tagsMap, 71 | valuesMap, 72 | ) 73 | } 74 | 75 | // https://stackoverflow.com/questions/29366038/looping-iterate-over-the-second-level-nested-json-in-go-lang 76 | func parseMap(timestamp time.Time, index int, pathPtr *string, measurementNamePtr *string, tagNames map[int]string, tagsMap map[string]string, valuesMap map[string]interface{}) { 77 | 78 | for key, val := range valuesMap { 79 | 80 | pathSplit := strings.Split(*pathPtr, ".") 81 | 82 | switch concreteVal := val.(type) { 83 | case map[string]interface{}: 84 | 85 | ok := false 86 | 87 | for i, v := range pathSplit { 88 | if v == key { 89 | tagName := pathSplit[i-1] 90 | tagsMap[tagName] = key 91 | ok = true 92 | } 93 | } 94 | 95 | if ok != true { 96 | tagsMap[tagNames[index]] = key 97 | index++ 98 | } 99 | 100 | parseMap( 101 | timestamp, 102 | index, 103 | pathPtr, 104 | measurementNamePtr, 105 | tagNames, 106 | tagsMap, 107 | val.(map[string]interface{}), 108 | ) 109 | 110 | default: 111 | 112 | if len(tagNames) != 0 { 113 | tagsMap[tagNames[index]] = key 114 | } else { 115 | for i, v := range pathSplit { 116 | if v == key { 117 | tagName := pathSplit[i-1] 118 | tagsMap[tagName] = key 119 | } 120 | } 121 | } 122 | 123 | // Formating tags set 124 | // =,= 125 | tagsMap["unity"] = unityName 126 | 127 | // Formating fied set 128 | // = 129 | fieldsMap := make(map[string]string) 130 | _, ok := concreteVal.(float64) 131 | 132 | if ok { 133 | fieldsMap[pathSplit[len(pathSplit)-1]] = fmt.Sprintf("%f", concreteVal) 134 | } else { 135 | fieldsMap[pathSplit[len(pathSplit)-1]] = fmt.Sprintf("%s", concreteVal) 136 | } 137 | 138 | // Formating and printing the result using the InfluxDB's Line Protocol 139 | // https://docs.influxdata.com/influxdb/v1.5/write_protocols/line_protocol_tutorial/ 140 | 141 | printInflux(*measurementNamePtr, tagsMap, fieldsMap, timestamp.UnixNano()) 142 | } 143 | } 144 | } 145 | 146 | func parsePool(id string, name string, sizeFree uint64, sizeSubscribed uint64, sizeTotal uint64, sizeUsed uint64) { 147 | 148 | tagsMap := make(map[string]string) 149 | fieldsMap := make(map[string]string) 150 | 151 | tagsMap["unity"] = unityName 152 | tagsMap["pool"] = id 153 | tagsMap["poolname"] = name 154 | 155 | fieldsMap["sizefree"] = strconv.FormatUint(sizeFree, 10) 156 | fieldsMap["sizesubscribed"] = strconv.FormatUint(sizeSubscribed, 10) 157 | fieldsMap["sizetotal"] = strconv.FormatUint(sizeTotal, 10) 158 | fieldsMap["sizeused"] = strconv.FormatUint(sizeUsed, 10) 159 | 160 | printInflux("pool", tagsMap, fieldsMap, time.Now().UnixNano()) 161 | } 162 | 163 | func parseStorageResource(id string, name string, sizeAllocated uint64, sizeTotal uint64, sizeUsed uint64) { 164 | 165 | tagsMap := make(map[string]string) 166 | fieldsMap := make(map[string]string) 167 | 168 | tagsMap["unity"] = unityName 169 | tagsMap["storageresource"] = id 170 | tagsMap["storageresourcename"] = name 171 | 172 | fieldsMap["sizeallocated"] = strconv.FormatUint(sizeAllocated, 10) 173 | fieldsMap["sizetotal"] = strconv.FormatUint(sizeTotal, 10) 174 | fieldsMap["sizeused"] = strconv.FormatUint(sizeUsed, 10) 175 | 176 | printInflux("storageresource", tagsMap, fieldsMap, time.Now().UnixNano()) 177 | } 178 | 179 | func parseKpiValue(id string, name string, path string, value float64) { 180 | 181 | pathSplit := strings.Split(path, ".") 182 | tagsMap := make(map[string]string) 183 | fieldsMap := make(map[string]string) 184 | 185 | tagsMap["unity"] = unityName 186 | 187 | for i, v := range pathSplit { 188 | if v == id { 189 | tagName := strings.ToLower(pathSplit[i-1]) 190 | tagsMap[tagName] = v 191 | tagsMap[tagName+"name"] = strings.Replace(name, " ", "_", -1) 192 | } 193 | if v == "sp" || v == "rw" || v == "lun" { 194 | tagsMap["lun"] = pathSplit[i+1] 195 | } 196 | } 197 | 198 | fieldsMap[pathSplit[len(pathSplit)-1]] = fmt.Sprintf("%f", value) 199 | 200 | printInflux("kpi_"+pathSplit[1], tagsMap, fieldsMap, time.Now().UnixNano()) 201 | } 202 | 203 | // printInflux purpose is to output data in the influxdb line format 204 | func printInflux(measurement string, tagsMap map[string]string, fieldsMap map[string]string, timestamp int64) { 205 | 206 | // Parse tagsMap 207 | var tags string 208 | var i int 209 | for k, v := range tagsMap { 210 | if i == 0 { 211 | tags = tags + fmt.Sprintf("%s=%s", k, v) 212 | } else { 213 | tags = tags + fmt.Sprintf(",%s=%s", k, v) 214 | } 215 | i++ 216 | } 217 | 218 | // Parse fieldsMap 219 | var fields string 220 | var j int 221 | for k, v := range fieldsMap { 222 | if j == 0 { 223 | fields = fields + fmt.Sprintf("%s=%s", k, v) 224 | } else { 225 | fields = fields + fmt.Sprintf(",%s=%s", k, v) 226 | } 227 | j++ 228 | } 229 | 230 | fmt.Printf("%s,%s %s %d\n", measurement, tags, fields, timestamp) 231 | } 232 | 233 | func main() { 234 | 235 | // Set logs parameters 236 | log.Out = os.Stdout 237 | 238 | userPtr := flag.String("user", "", "Username") 239 | passwordPtr := flag.String("password", "", "Password") 240 | unityPtr := flag.String("unity", "", "Unity IP or FQDN") 241 | intervalPtr := flag.Uint64("interval", 30, "Sampling interval") 242 | rtpathsPtr := flag.String("rtpaths", "", "Real time metrics paths") 243 | histpathsPtr := flag.String("histpaths", "", "Historical metrics paths") 244 | histkpipathsPtr := flag.String("histkpipaths", "", "Historical KPI metrics paths") 245 | capacityPtr := flag.Bool("capacity", false, "Display capacity statisitcs") 246 | debugPtr := flag.Bool("debug", false, "Debug mode") 247 | 248 | flag.Parse() 249 | 250 | if *debugPtr == true { 251 | log.Level = logrus.DebugLevel 252 | } else { 253 | log.Level = logrus.ErrorLevel 254 | } 255 | 256 | log.WithFields(logrus.Fields{ 257 | "event": "flag", 258 | "key": "user", 259 | "value": *userPtr, 260 | }).Debug("Parsed flag user") 261 | 262 | log.WithFields(logrus.Fields{ 263 | "event": "flag", 264 | "key": "unity", 265 | "value": *unityPtr, 266 | }).Debug("Parsed flag unity") 267 | 268 | log.WithFields(logrus.Fields{ 269 | "event": "flag", 270 | "key": "interval", 271 | "value": *intervalPtr, 272 | }).Debug("Parsed flag interval") 273 | 274 | log.WithFields(logrus.Fields{ 275 | "event": "flag", 276 | "key": "paths", 277 | "value": *rtpathsPtr, 278 | }).Debug("Parsed flag real time metrics paths") 279 | 280 | log.WithFields(logrus.Fields{ 281 | "event": "flag", 282 | "key": "paths", 283 | "value": *histpathsPtr, 284 | }).Debug("Parsed flag historical metrics paths") 285 | 286 | log.WithFields(logrus.Fields{ 287 | "event": "flag", 288 | "key": "paths", 289 | "value": *histkpipathsPtr, 290 | }).Debug("Parsed flag historical KPI metrics paths") 291 | 292 | log.WithFields(logrus.Fields{ 293 | "event": "flag", 294 | "key": "capacity", 295 | "value": *capacityPtr, 296 | }).Debug("Parsed flag capacity") 297 | 298 | // Start a new Unity session 299 | 300 | log.WithFields(logrus.Fields{ 301 | "event": "gounity.NewSession", 302 | "unity": *unityPtr, 303 | "engineering": "true", 304 | "user": *userPtr, 305 | }).Debug("Started new Unity session") 306 | 307 | session, err := gounity.NewSession(*unityPtr, true, *userPtr, *passwordPtr) 308 | 309 | if err != nil { 310 | log.Fatal(err) 311 | } 312 | 313 | defer session.CloseSession() 314 | 315 | // Get system informations 316 | System, err := session.GetbasicSystemInfo() 317 | if err != nil { 318 | log.Fatal(err) 319 | } else { 320 | // Store the name of the Unity 321 | unityName = System.Entries[0].Content.Name 322 | } 323 | 324 | // Store pools informations 325 | Pools, err := session.GetPool() 326 | if err != nil { 327 | log.Fatal(err) 328 | } else { 329 | for _, p := range Pools.Entries { 330 | unityPools = append(unityPools, p.Content) 331 | } 332 | } 333 | 334 | // Store storage resources informations 335 | StorageResources, err := session.GetStorageResource() 336 | if err != nil { 337 | log.Fatal(err) 338 | } else { 339 | for _, s := range StorageResources.Entries { 340 | unityStorageResource = append(unityStorageResource, s.Content) 341 | } 342 | } 343 | 344 | if *histkpipathsPtr != "" { 345 | // Request a new kpi query 346 | histkpipaths := strings.Split(*histkpipathsPtr, ",") 347 | 348 | for _, p := range histkpipaths { 349 | 350 | KpiValue, err := session.GetkpiValue(p) 351 | if err != nil { 352 | log.WithFields(logrus.Fields{ 353 | "event": "historical", 354 | "key": "paths", 355 | "value": p, 356 | "error": err, 357 | }).Error("Querying kpi historical metric(s)") 358 | } else { 359 | for _, k := range KpiValue.Entries { 360 | parseKpiValue(k.Content.ID, k.Content.Name, k.Content.Path, k.Content.Values[k.Content.EndTime]) 361 | } 362 | 363 | } 364 | } 365 | } 366 | 367 | if *capacityPtr { 368 | // Parse pool info into influxdb line protocol 369 | for _, p := range unityPools { 370 | parsePool(p.ID, p.Name, p.SizeFree, p.SizeSubscribed, p.SizeTotal, p.SizeUsed) 371 | } 372 | 373 | // Parse storage resources info into influxdb line protocol 374 | for _, s := range unityStorageResource { 375 | parseStorageResource(s.ID, s.Name, s.SizeAllocated, s.SizeTotal, s.SizeUsed) 376 | } 377 | } 378 | 379 | if *histpathsPtr != "" { 380 | 381 | // metric paths 382 | histpaths := strings.Split(*histpathsPtr, ",") 383 | 384 | for _, p := range histpaths { 385 | 386 | log.WithFields(logrus.Fields{ 387 | "event": "historical", 388 | "key": "paths", 389 | "value": p, 390 | }).Debug("Querying historical metric") 391 | 392 | // Request a new metric query 393 | MetricValue, err := session.GetmetricValue(p) 394 | if err != nil { 395 | log.WithFields(logrus.Fields{ 396 | "event": "historical", 397 | "key": "paths", 398 | "value": p, 399 | "error": err, 400 | }).Error("Querying historical metric(s)") 401 | } else { 402 | parseResult(MetricValue.Entries[0].Content.Timestamp, MetricValue.Entries[0].Content.Path, MetricValue.Entries[0].Content.Values.(map[string]interface{})) 403 | } 404 | } 405 | } 406 | 407 | if *rtpathsPtr != "" { 408 | 409 | // metric paths 410 | rtpaths := strings.Split(*rtpathsPtr, ",") 411 | 412 | // converting metric interval into uint32 413 | var interval = uint32(*intervalPtr) 414 | 415 | // Request a new metric query 416 | Metric, err := session.NewMetricRealTimeQuery(rtpaths, interval) 417 | if err != nil { 418 | log.Fatal(err) 419 | } 420 | 421 | // Waiting thforat the sampling of the metrics to be done 422 | time.Sleep(time.Duration(Metric.Content.Interval) * time.Second) 423 | 424 | // Get the results of the query 425 | Result, err := session.GetMetricRealTimeQueryResult(Metric.Content.ID) 426 | if err != nil { 427 | log.WithFields(logrus.Fields{ 428 | "event": "realtime", 429 | "key": "error", 430 | "error": err, 431 | }).Error("Querying real time metric(s)") 432 | } else { 433 | // Parse the results 434 | for _, v := range Result.Entries { 435 | 436 | parseResult(v.Content.Timestamp, v.Content.Path, v.Content.Values.(map[string]interface{})) 437 | } 438 | } 439 | } 440 | } 441 | --------------------------------------------------------------------------------