github.com/psiphon-inc/goarista@v0.0.0-20160825065156-d002785f4c67/cmd/octsdb/main.go (about)

     1  // Copyright (C) 2016  Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  // The octsdb tool pushes OpenConfig telemetry to OpenTSDB.
     6  package main
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/json"
    11  	"flag"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  
    16  	"github.com/aristanetworks/glog"
    17  	"github.com/aristanetworks/goarista/openconfig"
    18  	"github.com/aristanetworks/goarista/openconfig/client"
    19  )
    20  
    21  func main() {
    22  	tsdbFlag := flag.String("tsdb", "",
    23  		"Address of the OpenTSDB server where to push telemetry to")
    24  	textFlag := flag.Bool("text", false,
    25  		"Print the output as simple text")
    26  	configFlag := flag.String("config", "",
    27  		"Config to turn OpenConfig telemetry into OpenTSDB put requests")
    28  	username, password, subscriptions, addrs, opts := client.ParseFlags()
    29  
    30  	if !(*tsdbFlag != "" || *textFlag) {
    31  		glog.Fatal("Specify the address of the OpenTSDB server to write to with -tsdb")
    32  	} else if *configFlag == "" {
    33  		glog.Fatal("Specify a JSON configuration file with -config")
    34  	}
    35  
    36  	config, err := loadConfig(*configFlag)
    37  	if err != nil {
    38  		glog.Fatal(err)
    39  	}
    40  	// Ignore the default "subscribe-to-everything" subscription of the
    41  	// -subscribe flag.
    42  	if subscriptions[0] == "" {
    43  		subscriptions = subscriptions[1:]
    44  	}
    45  	// Add the subscriptions from the config file.
    46  	subscriptions = append(subscriptions, config.Subscriptions...)
    47  
    48  	var c OpenTSDBConn
    49  	if *textFlag {
    50  		c = newTextDumper()
    51  	} else {
    52  		// TODO: support HTTP(S).
    53  		c = newTelnetClient(*tsdbFlag)
    54  	}
    55  
    56  	wg := new(sync.WaitGroup)
    57  	for _, addr := range addrs {
    58  		wg.Add(1)
    59  		publish := func(resp *openconfig.SubscribeResponse) {
    60  			if notif := resp.GetUpdate(); notif != nil {
    61  				pushToOpenTSDB(addr, c, config, notif)
    62  			}
    63  		}
    64  		go client.Run(publish, wg, username, password, addr, subscriptions, opts)
    65  	}
    66  	wg.Wait()
    67  }
    68  
    69  func pushToOpenTSDB(addr string, conn OpenTSDBConn, config *Config,
    70  	notif *openconfig.Notification) {
    71  
    72  	if notif.Timestamp <= 0 {
    73  		glog.Fatalf("Invalid timestamp %d in %s", notif.Timestamp, notif)
    74  	}
    75  
    76  	host := addr[:strings.IndexRune(addr, ':')]
    77  	prefix := "/" + strings.Join(notif.Prefix.Element, "/")
    78  	for _, update := range notif.Update {
    79  		if update.Value == nil || update.Value.Type != openconfig.Type_JSON {
    80  			glog.V(9).Infof("Ignoring incompatible update value in %s", update)
    81  			continue
    82  		}
    83  
    84  		value := parseValue(update)
    85  		if value == nil {
    86  			glog.V(9).Infof("Ignoring non-numeric value in %s", update)
    87  			continue
    88  		}
    89  
    90  		path := prefix + "/" + strings.Join(update.Path.Element, "/")
    91  		metricName, tags := config.Match(path)
    92  		if metricName == "" {
    93  			glog.V(8).Infof("Ignoring unmatched update at %s: %+v", path, update.Value)
    94  			continue
    95  		}
    96  		tags["host"] = host
    97  
    98  		conn.Put(&DataPoint{
    99  			Metric:    metricName,
   100  			Timestamp: uint64(notif.Timestamp),
   101  			Value:     value,
   102  			Tags:      tags,
   103  		})
   104  	}
   105  }
   106  
   107  // parseValue returns the integer or floating point value of the given update,
   108  // or nil if it's not a numerical update.
   109  func parseValue(update *openconfig.Update) (value interface{}) {
   110  	decoder := json.NewDecoder(bytes.NewReader(update.Value.Value))
   111  	decoder.UseNumber()
   112  	err := decoder.Decode(&value)
   113  	if err != nil {
   114  		glog.Fatalf("Malformed JSON update %q in %s", update.Value.Value, update)
   115  	}
   116  	num, ok := value.(json.Number)
   117  	if !ok {
   118  		return nil
   119  	}
   120  	// Convert our json.Number to either an int64, uint64, or float64.
   121  	if value, err = num.Int64(); err != nil {
   122  		// num is either a large unsigned integer or a floating point.
   123  		if strings.Contains(err.Error(), "value out of range") { // Sigh.
   124  			value, err = strconv.ParseUint(num.String(), 10, 64)
   125  		} else {
   126  			value, err = num.Float64()
   127  			if err != nil {
   128  				glog.Fatalf("Malformed JSON number %q in %s", num, update)
   129  			}
   130  		}
   131  	}
   132  	return
   133  }