github.com/psiphon-labs/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 }