// storeStatistics writes the statistics to an InfluxDB system. func (m *Monitor) storeStatistics() { defer m.wg.Done() m.Logger.Printf("Storing statistics in database '%s' retention policy '%s', at interval %s", m.storeDatabase, m.storeRetentionPolicy, m.storeInterval) hostname, _ := os.Hostname() m.SetGlobalTag("hostname", hostname) // Wait until an even interval to start recording monitor statistics. // If we are interrupted before the interval for some reason, exit early. if err := m.waitUntilInterval(m.storeInterval); err != nil { return } tick := time.NewTicker(m.storeInterval) defer tick.Stop() for { select { case now := <-tick.C: now = now.Truncate(m.storeInterval) func() { m.mu.Lock() defer m.mu.Unlock() m.createInternalStorage() }() stats, err := m.Statistics(m.globalTags) if err != nil { m.Logger.Printf("failed to retrieve registered statistics: %s", err) return } points := make(models.Points, 0, len(stats)) for _, s := range stats { pt, err := models.NewPoint(s.Name, models.NewTags(s.Tags), s.Values, now) if err != nil { m.Logger.Printf("Dropping point %v: %v", s.Name, err) return } points = append(points, pt) } func() { m.mu.RLock() defer m.mu.RUnlock() if err := m.PointsWriter.WritePoints(m.storeDatabase, m.storeRetentionPolicy, points); err != nil { m.Logger.Printf("failed to store statistics: %s", err) } }() case <-m.done: m.Logger.Printf("terminating storage of statistics") return } } }
// AddPoint adds a point to the WritePointRequest with field key 'value' func (w *WritePointsRequest) AddPoint(name string, value interface{}, timestamp time.Time, tags map[string]string) { pt, err := models.NewPoint( name, models.NewTags(tags), map[string]interface{}{"value": value}, timestamp, ) if err != nil { return } w.Points = append(w.Points, pt) }
func Test_Service_UDP(t *testing.T) { t.Parallel() now := time.Now().UTC().Round(time.Second) config := Config{} config.Database = "graphitedb" config.BatchSize = 0 // No batching. config.BatchTimeout = toml.Duration(time.Second) config.BindAddress = ":10000" config.Protocol = "udp" service := NewTestService(&config) // Allow test to wait until points are written. var wg sync.WaitGroup wg.Add(1) service.WritePointsFn = func(database, retentionPolicy string, consistencyLevel models.ConsistencyLevel, points []models.Point) error { defer wg.Done() pt, _ := models.NewPoint( "cpu", models.NewTags(map[string]string{}), map[string]interface{}{"value": 23.456}, time.Unix(now.Unix(), 0)) if database != "graphitedb" { t.Fatalf("unexpected database: %s", database) } else if retentionPolicy != "" { t.Fatalf("unexpected retention policy: %s", retentionPolicy) } else if points[0].String() != pt.String() { t.Fatalf("unexpected points: %#v", points[0].String()) } return nil } if err := service.Service.Open(); err != nil { t.Fatalf("failed to open Graphite service: %s", err.Error()) } // Connect to the graphite endpoint we just spun up _, port, _ := net.SplitHostPort(service.Service.Addr().String()) conn, err := net.Dial("udp", "127.0.0.1:"+port) if err != nil { t.Fatal(err) } data := []byte(`cpu 23.456 `) data = append(data, []byte(fmt.Sprintf("%d", now.Unix()))...) data = append(data, '\n') _, err = conn.Write(data) if err != nil { t.Fatal(err) } wg.Wait() conn.Close() }
// AddPoint adds a new time series point func (w *WriteShardRequest) AddPoint(name string, value interface{}, timestamp time.Time, tags map[string]string) { pt, err := models.NewPoint( name, tags, map[string]interface{}{"value": value}, timestamp, ) if err != nil { return } w.AddPoints([]models.Point{pt}) }
// MarshalString renders string representation of a Point with specified // precision. The default precision is nanoseconds. func (p *Point) MarshalString() string { pt, err := models.NewPoint(p.Measurement, p.Tags, p.Fields, p.Time) if err != nil { return "# ERROR: " + err.Error() + " " + p.Measurement } if p.Precision == "" || p.Precision == "ns" || p.Precision == "n" { return pt.String() } return pt.PrecisionString(p.Precision) }
// storeStatistics writes the statistics to an InfluxDB system. func (m *Monitor) storeStatistics() { defer m.wg.Done() m.Logger.Printf("Storing statistics in database '%s' retention policy '%s', at interval %s", m.storeDatabase, m.storeRetentionPolicy, m.storeInterval) // Get cluster-level metadata. Nothing different is going to happen if errors occur. clusterID := m.MetaClient.ClusterID() hostname, _ := os.Hostname() clusterTags := map[string]string{ "clusterID": fmt.Sprintf("%d", clusterID), "nodeID": fmt.Sprintf("%d", m.NodeID), "hostname": hostname, } tick := time.NewTicker(m.storeInterval) defer tick.Stop() for { select { case <-tick.C: m.createInternalStorage() stats, err := m.Statistics(clusterTags) if err != nil { m.Logger.Printf("failed to retrieve registered statistics: %s", err) continue } points := make(models.Points, 0, len(stats)) for _, s := range stats { pt, err := models.NewPoint(s.Name, s.Tags, s.Values, time.Now().Truncate(time.Second)) if err != nil { m.Logger.Printf("Dropping point %v: %v", s.Name, err) continue } points = append(points, pt) } err = m.PointsWriter.WritePoints(&cluster.WritePointsRequest{ Database: m.storeDatabase, RetentionPolicy: m.storeRetentionPolicy, ConsistencyLevel: cluster.ConsistencyLevelOne, Points: points, }) if err != nil { m.Logger.Printf("failed to store statistics: %s", err) } case <-m.done: m.Logger.Printf("terminating storage of statistics") return } } }
func TestNewPointsRejectsMaxKey(t *testing.T) { var key string for i := 0; i < 65536; i++ { key += "a" } if _, err := models.NewPoint(key, nil, models.Fields{"value": 1}, time.Now()); err == nil { t.Fatalf("new point with max key. got: nil, expected: error") } if _, err := models.ParsePointsString(fmt.Sprintf("%v value=1", key)); err == nil { t.Fatalf("parse point with max key. got: nil, expected: error") } }
// NewGaugeMetric returns a gauge metric. // Gauge metrics should be used when the metric is can arbitrarily go up and // down. ie, temperature, memory usage, cpu usage, etc. func NewGaugeMetric( name string, tags map[string]string, fields map[string]interface{}, t time.Time, ) (Metric, error) { pt, err := models.NewPoint(name, models.NewTags(tags), fields, t) if err != nil { return nil, err } return &metric{ pt: pt, mType: Gauge, }, nil }
// Unmarshal translates a collectd packet into InfluxDB data points. func (s *Service) UnmarshalCollectd(packet *gollectd.Packet) []models.Point { // Prefer high resolution timestamp. var timestamp time.Time if packet.TimeHR > 0 { // TimeHR is "near" nanosecond measurement, but not exactly nanasecond time // Since we store time in microseconds, we round here (mostly so tests will work easier) sec := packet.TimeHR >> 30 // Shifting, masking, and dividing by 1 billion to get nanoseconds. nsec := ((packet.TimeHR & 0x3FFFFFFF) << 30) / 1000 / 1000 / 1000 timestamp = time.Unix(int64(sec), int64(nsec)).UTC().Round(time.Microsecond) } else { // If we don't have high resolution time, fall back to basic unix time timestamp = time.Unix(int64(packet.Time), 0).UTC() } var points []models.Point for i := range packet.Values { name := fmt.Sprintf("%s_%s", packet.Plugin, packet.Values[i].Name) tags := make(map[string]string) fields := make(map[string]interface{}) fields["value"] = packet.Values[i].Value if packet.Hostname != "" { tags["host"] = packet.Hostname } if packet.PluginInstance != "" { tags["instance"] = packet.PluginInstance } if packet.Type != "" { tags["type"] = packet.Type } if packet.TypeInstance != "" { tags["type_instance"] = packet.TypeInstance } // Drop invalid points p, err := models.NewPoint(name, models.NewTags(tags), fields, timestamp) if err != nil { s.Logger.Printf("Dropping point %v: %v", name, err) atomic.AddInt64(&s.stats.InvalidDroppedPoints, 1) continue } points = append(points, p) } return points }
// storeStatistics writes the statistics to an InfluxDB system. func (m *Monitor) storeStatistics() { defer m.wg.Done() m.Logger.Printf("Storing statistics in database '%s' retention policy '%s', at interval %s", m.storeDatabase, m.storeRetentionPolicy, m.storeInterval) hostname, _ := os.Hostname() m.SetGlobalTag("hostname", hostname) m.mu.Lock() tick := time.NewTicker(m.storeInterval) m.mu.Unlock() defer tick.Stop() for { select { case <-tick.C: func() { m.mu.Lock() defer m.mu.Unlock() m.createInternalStorage() stats, err := m.Statistics(m.globalTags) if err != nil { m.Logger.Printf("failed to retrieve registered statistics: %s", err) return } points := make(models.Points, 0, len(stats)) for _, s := range stats { pt, err := models.NewPoint(s.Name, s.Tags, s.Values, time.Now().Truncate(time.Second)) if err != nil { m.Logger.Printf("Dropping point %v: %v", s.Name, err) return } points = append(points, pt) } if err := m.PointsWriter.WritePoints(m.storeDatabase, m.storeRetentionPolicy, points); err != nil { m.Logger.Printf("failed to store statistics: %s", err) } }() case <-m.done: m.Logger.Printf("terminating storage of statistics") return } } }
// Unmarshal translates a ValueList into InfluxDB data points. func (s *Service) UnmarshalValueList(vl *api.ValueList) []models.Point { timestamp := vl.Time.UTC() var points []models.Point for i := range vl.Values { var name string name = fmt.Sprintf("%s_%s", vl.Identifier.Plugin, vl.DSName(i)) tags := make(map[string]string) fields := make(map[string]interface{}) // Convert interface back to actual type, then to float64 switch value := vl.Values[i].(type) { case api.Gauge: fields["value"] = float64(value) case api.Derive: fields["value"] = float64(value) case api.Counter: fields["value"] = float64(value) } if vl.Identifier.Host != "" { tags["host"] = vl.Identifier.Host } if vl.Identifier.PluginInstance != "" { tags["instance"] = vl.Identifier.PluginInstance } if vl.Identifier.Type != "" { tags["type"] = vl.Identifier.Type } if vl.Identifier.TypeInstance != "" { tags["type_instance"] = vl.Identifier.TypeInstance } // Drop invalid points p, err := models.NewPoint(name, models.NewTags(tags), fields, timestamp) if err != nil { s.Logger.Info(fmt.Sprintf("Dropping point %v: %v", name, err)) atomic.AddInt64(&s.stats.InvalidDroppedPoints, 1) continue } points = append(points, p) } return points }
// NewPoint returns a point with the given timestamp. If a timestamp is not // given, then data is sent to the database without a timestamp, in which case // the server will assign local time upon reception. NOTE: it is recommended to // send data with a timestamp. func NewPoint( name string, tags map[string]string, fields map[string]interface{}, t ...time.Time, ) (*Point, error) { var T time.Time if len(t) > 0 { T = t[0] } pt, err := models.NewPoint(name, tags, fields, T) if err != nil { return nil, err } return &Point{ pt: pt, }, nil }
// NormalizeBatchPoints returns a slice of Points, created by populating individual // points within the batch, which do not have times or tags, with the top-level // values. func NormalizeBatchPoints(bp client.BatchPoints) ([]models.Point, error) { points := []models.Point{} for _, p := range bp.Points { if p.Time.IsZero() { if bp.Time.IsZero() { p.Time = time.Now() } else { p.Time = bp.Time } } if p.Precision == "" && bp.Precision != "" { p.Precision = bp.Precision } p.Time = client.SetPrecision(p.Time, p.Precision) if len(bp.Tags) > 0 { if p.Tags == nil { p.Tags = make(map[string]string) } for k := range bp.Tags { if p.Tags[k] == "" { p.Tags[k] = bp.Tags[k] } } } if p.Measurement == "" { return points, fmt.Errorf("missing measurement") } if len(p.Fields) == 0 { return points, fmt.Errorf("missing fields") } // Need to convert from a client.Point to a influxdb.Point pt, err := models.NewPoint(p.Measurement, p.Tags, p.Fields, p.Time) if err != nil { return points, err } points = append(points, pt) } return points, nil }
// convertRowToPoints will convert a query result Row into Points that can be written back in. func convertRowToPoints(measurementName string, row *models.Row) ([]models.Point, error) { // figure out which parts of the result are the time and which are the fields timeIndex := -1 fieldIndexes := make(map[string]int) for i, c := range row.Columns { if c == "time" { timeIndex = i } else { fieldIndexes[c] = i } } if timeIndex == -1 { return nil, errors.New("error finding time index in result") } points := make([]models.Point, 0, len(row.Values)) for _, v := range row.Values { vals := make(map[string]interface{}) for fieldName, fieldIndex := range fieldIndexes { val := v[fieldIndex] if val != nil { vals[fieldName] = v[fieldIndex] } } p, err := models.NewPoint(measurementName, models.NewTags(row.Tags), vals, v[timeIndex].(time.Time)) if err != nil { // Drop points that can't be stored continue } points = append(points, p) } return points, nil }
func TestNewPointWithoutField(t *testing.T) { _, err := models.NewPoint("cpu", models.Tags{"tag": "bar"}, models.Fields{}, time.Unix(0, 0)) if err == nil { t.Fatalf(`NewPoint() expected error. got nil`) } }
// handleTelnetConn accepts OpenTSDB's telnet protocol. // Each telnet command consists of a line of the form: // put sys.cpu.user 1356998400 42.5 host=webserver01 cpu=0 func (s *Service) handleTelnetConn(conn net.Conn) { defer conn.Close() defer s.wg.Done() defer s.statMap.Add(statTelnetConnectionsActive, -1) s.statMap.Add(statTelnetConnectionsActive, 1) s.statMap.Add(statTelnetConnectionsHandled, 1) // Get connection details. remoteAddr := conn.RemoteAddr().String() // Wrap connection in a text protocol reader. r := textproto.NewReader(bufio.NewReader(conn)) for { line, err := r.ReadLine() if err != nil { if err != io.EOF { s.statMap.Add(statTelnetReadError, 1) s.Logger.Println("error reading from openTSDB connection", err.Error()) } return } s.statMap.Add(statTelnetPointsReceived, 1) s.statMap.Add(statTelnetBytesReceived, int64(len(line))) inputStrs := strings.Fields(line) if len(inputStrs) == 1 && inputStrs[0] == "version" { conn.Write([]byte("InfluxDB TSDB proxy")) continue } if len(inputStrs) < 4 || inputStrs[0] != "put" { s.statMap.Add(statTelnetBadLine, 1) if s.LogPointErrors { s.Logger.Printf("malformed line '%s' from %s", line, remoteAddr) } continue } measurement := inputStrs[1] tsStr := inputStrs[2] valueStr := inputStrs[3] tagStrs := inputStrs[4:] var t time.Time ts, err := strconv.ParseInt(tsStr, 10, 64) if err != nil { s.statMap.Add(statTelnetBadTime, 1) if s.LogPointErrors { s.Logger.Printf("malformed time '%s' from %s", tsStr, remoteAddr) } } switch len(tsStr) { case 10: t = time.Unix(ts, 0) break case 13: t = time.Unix(ts/1000, (ts%1000)*1000) break default: s.statMap.Add(statTelnetBadTime, 1) if s.LogPointErrors { s.Logger.Printf("bad time '%s' must be 10 or 13 chars, from %s ", tsStr, remoteAddr) } continue } tags := make(map[string]string) for t := range tagStrs { parts := strings.SplitN(tagStrs[t], "=", 2) if len(parts) != 2 || parts[0] == "" || parts[1] == "" { s.statMap.Add(statTelnetBadTag, 1) if s.LogPointErrors { s.Logger.Printf("malformed tag data '%v' from %s", tagStrs[t], remoteAddr) } continue } k := parts[0] tags[k] = parts[1] } fields := make(map[string]interface{}) fv, err := strconv.ParseFloat(valueStr, 64) if err != nil { s.statMap.Add(statTelnetBadFloat, 1) if s.LogPointErrors { s.Logger.Printf("bad float '%s' from %s", valueStr, remoteAddr) } continue } fields["value"] = fv pt, err := models.NewPoint(measurement, tags, fields, t) if err != nil { s.statMap.Add(statTelnetBadFloat, 1) if s.LogPointErrors { s.Logger.Printf("bad float '%s' from %s", valueStr, remoteAddr) } continue } s.batcher.In() <- pt } }
func Test_ServerGraphiteUDP(t *testing.T) { t.Parallel() now := time.Now().UTC().Round(time.Second) config := graphite.Config{} config.Database = "graphitedb" config.BatchSize = 0 // No batching. config.BatchTimeout = toml.Duration(time.Second) config.BindAddress = ":10000" config.Protocol = "udp" service, err := graphite.NewService(config) if err != nil { t.Fatalf("failed to create Graphite service: %s", err.Error()) } // Allow test to wait until points are written. var wg sync.WaitGroup wg.Add(1) pointsWriter := PointsWriter{ WritePointsFn: func(req *cluster.WritePointsRequest) error { defer wg.Done() pt, _ := models.NewPoint( "cpu", map[string]string{}, map[string]interface{}{"value": 23.456}, time.Unix(now.Unix(), 0)) if req.Database != "graphitedb" { t.Fatalf("unexpected database: %s", req.Database) } else if req.RetentionPolicy != "" { t.Fatalf("unexpected retention policy: %s", req.RetentionPolicy) } else if req.Points[0].String() != pt.String() { t.Fatalf("unexpected points: %#v", req.Points[0].String()) } return nil }, } service.PointsWriter = &pointsWriter dbCreator := DatabaseCreator{} service.MetaClient = &dbCreator if err := service.Open(); err != nil { t.Fatalf("failed to open Graphite service: %s", err.Error()) } if !dbCreator.Created { t.Fatalf("failed to create target database") } // Connect to the graphite endpoint we just spun up _, port, _ := net.SplitHostPort(service.Addr().String()) conn, err := net.Dial("udp", "127.0.0.1:"+port) if err != nil { t.Fatal(err) } data := []byte(`cpu 23.456 `) data = append(data, []byte(fmt.Sprintf("%d", now.Unix()))...) data = append(data, '\n') _, err = conn.Write(data) if err != nil { t.Fatal(err) } wg.Wait() conn.Close() }
func TestNewPointsRejectsEmptyFieldNames(t *testing.T) { if _, err := models.NewPoint("foo", nil, models.Fields{"": 1}, time.Now()); err == nil { t.Fatalf("new point with empty field name. got: nil, expected: error") } }
// ServeHTTP implements OpenTSDB's HTTP /api/put endpoint func (h *Handler) servePut(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() // Require POST method. if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } // Wrap reader if it's gzip encoded. var br *bufio.Reader if r.Header.Get("Content-Encoding") == "gzip" { zr, err := gzip.NewReader(r.Body) if err != nil { http.Error(w, "could not read gzip, "+err.Error(), http.StatusBadRequest) return } br = bufio.NewReader(zr) } else { br = bufio.NewReader(r.Body) } // Lookahead at the first byte. f, err := br.Peek(1) if err != nil || len(f) != 1 { http.Error(w, "peek error: "+err.Error(), http.StatusBadRequest) return } // Peek to see if this is a JSON array. var multi bool switch f[0] { case '{': case '[': multi = true default: http.Error(w, "expected JSON array or hash", http.StatusBadRequest) return } // Decode JSON data into slice of points. dps := make([]point, 1) if dec := json.NewDecoder(br); multi { if err = dec.Decode(&dps); err != nil { http.Error(w, "json array decode error", http.StatusBadRequest) return } } else { if err = dec.Decode(&dps[0]); err != nil { http.Error(w, "json object decode error", http.StatusBadRequest) return } } // Convert points into TSDB points. points := make([]models.Point, 0, len(dps)) for i := range dps { p := dps[i] // Convert timestamp to Go time. // If time value is over a billion then it's microseconds. var ts time.Time if p.Time < 10000000000 { ts = time.Unix(p.Time, 0) } else { ts = time.Unix(p.Time/1000, (p.Time%1000)*1000) } pt, err := models.NewPoint(p.Metric, p.Tags, map[string]interface{}{"value": p.Value}, ts) if err != nil { h.Logger.Printf("Dropping point %v: %v", p.Metric, err) h.statMap.Add(statDroppedPointsInvalid, 1) continue } points = append(points, pt) } // Write points. if err := h.PointsWriter.WritePoints(h.Database, h.RetentionPolicy, models.ConsistencyLevelAny, points); influxdb.IsClientError(err) { h.Logger.Println("write series error: ", err) http.Error(w, "write series error: "+err.Error(), http.StatusBadRequest) return } else if err != nil { h.Logger.Println("write series error: ", err) http.Error(w, "write series error: "+err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) }
// Parse performs Graphite parsing of a single line. func (p *Parser) Parse(line string) (models.Point, error) { // Break into 3 fields (name, value, timestamp). fields := strings.Fields(line) if len(fields) != 2 && len(fields) != 3 { return nil, fmt.Errorf("received %q which doesn't have required fields", line) } // decode the name and tags template := p.matcher.Match(fields[0]) measurement, tags, field, err := template.Apply(fields[0]) if err != nil { return nil, err } // Could not extract measurement, use the raw value if measurement == "" { measurement = fields[0] } // Parse value. v, err := strconv.ParseFloat(fields[1], 64) if err != nil { return nil, fmt.Errorf(`field "%s" value: %s`, fields[0], err) } if math.IsNaN(v) || math.IsInf(v, 0) { return nil, &UnsupportedValueError{Field: fields[0], Value: v} } fieldValues := map[string]interface{}{} if field != "" { fieldValues[field] = v } else { fieldValues["value"] = v } // If no 3rd field, use now as timestamp timestamp := time.Now().UTC() if len(fields) == 3 { // Parse timestamp. unixTime, err := strconv.ParseFloat(fields[2], 64) if err != nil { return nil, fmt.Errorf(`field "%s" time: %s`, fields[0], err) } // -1 is a special value that gets converted to current UTC time // See https://github.com/graphite-project/carbon/issues/54 if unixTime != float64(-1) { // Check if we have fractional seconds timestamp = time.Unix(int64(unixTime), int64((unixTime-math.Floor(unixTime))*float64(time.Second))) if timestamp.Before(MinDate) || timestamp.After(MaxDate) { return nil, fmt.Errorf("timestamp out of range") } } } // Set the default tags on the point if they are not already set for _, t := range p.tags { if _, ok := tags[string(t.Key)]; !ok { tags[string(t.Key)] = string(t.Value) } } return models.NewPoint(measurement, models.NewTags(tags), fieldValues, timestamp) }