| package tc |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import ( |
| "encoding/json" |
| "errors" |
| "fmt" |
| "regexp" |
| "strconv" |
| "strings" |
| "time" |
| |
| "github.com/apache/trafficcontrol/lib/go-log" |
| |
| influx "github.com/influxdata/influxdb/client/v2" |
| ) |
| |
| // TrafficStatsDurationPattern reflects all the possible durations that can be requested via the |
| // /deliveryservice_stats endpoint. |
| var TrafficStatsDurationPattern = regexp.MustCompile(`^\d+[mhdw]$`) |
| |
| // DurationLiteralToSeconds returns the number of seconds to which an InfluxQL duration literal is |
| // equivalent. For invalid objects, it returns -1 - otherwise it will always, of course be > 0. |
| func DurationLiteralToSeconds(d string) (int64, error) { |
| if strings.HasSuffix(d, "m") { |
| v, err := strconv.ParseInt(strings.Split(d, "m")[0], 10, 64) |
| return v * 60, err |
| } |
| |
| if strings.HasSuffix(d, "h") { |
| v, err := strconv.ParseInt(strings.Split(d, "h")[0], 10, 64) |
| return v * 3600, err |
| } |
| |
| if strings.HasSuffix(d, "d") { |
| v, err := strconv.ParseInt(strings.Split(d, "d")[0], 10, 64) |
| return v * 86400, err |
| } |
| |
| if strings.HasSuffix(d, "w") { |
| v, err := strconv.ParseInt(strings.Split(d, "w")[0], 10, 64) |
| return v * 604800, err |
| } |
| |
| return -1, errors.New("Invalid duration literal, no recognized suffix") |
| } |
| |
| // TrafficStatsOrderable encodes what columns by which the data returned from a Traffic Stats query |
| // may be ordered. |
| type TrafficStatsOrderable string |
| |
| // TimeOrder indicates an ordering by time at which the measurement was taken. |
| const TimeOrder TrafficStatsOrderable = "time" |
| |
| // OrderableFromString parses the passed string and returns the corresponding value as a pointer to |
| // a TrafficStatsOrderable - or nil if the value was invalid. |
| func OrderableFromString(v string) *TrafficStatsOrderable { |
| var o TrafficStatsOrderable |
| switch v { |
| case "time": |
| o = TimeOrder |
| default: |
| return nil |
| } |
| return &o |
| } |
| |
| // TrafficStatsExclude encodes what parts of a response to a request to a "Traffic Stats" endpoint |
| // of the TO API may be omitted. |
| type TrafficStatsExclude string |
| |
| const ( |
| // ExcludeSeries can be used to omit the data series from a response. |
| ExcludeSeries TrafficStatsExclude = "series" |
| |
| // ExcludeSummary can be used to omit the summary series from a response. |
| ExcludeSummary TrafficStatsExclude = "summary" |
| |
| // ExcludeInvalid can be used if the the key that you want to exclude fails |
| // validation. |
| ExcludeInvalid TrafficStatsExclude = "INVALID" |
| ) |
| |
| // ExcludeFromString parses the passed string and returns the corresponding value as a TrafficStatsExclude. |
| func ExcludeFromString(v string) TrafficStatsExclude { |
| switch v { |
| case "series": |
| return ExcludeSeries |
| case "summary": |
| return ExcludeSummary |
| default: |
| return ExcludeInvalid |
| } |
| } |
| |
| // TrafficStatsConfig represents the configuration of a request made to Traffic Stats. This is |
| // typically constructed by parsing a request body submitted to Traffic Ops. |
| type TrafficStatsConfig struct { |
| End time.Time |
| ExcludeSeries bool |
| ExcludeSummary bool |
| Interval string |
| Limit *uint64 |
| MetricType string |
| Offset *uint64 |
| OrderBy *TrafficStatsOrderable |
| Start time.Time |
| Unix bool |
| } |
| |
| // TrafficDSStatsConfig represents the configuration of a request made to |
| // Traffic Stats for Delivery Service stats. |
| type TrafficDSStatsConfig struct { |
| DeliveryService string |
| TrafficStatsConfig |
| } |
| |
| // TrafficCacheStatsConfig represents the configuration of a request made to |
| // Traffic Stats for cache server stats. |
| type TrafficCacheStatsConfig struct { |
| CDN string |
| TrafficStatsConfig |
| } |
| |
| // OffsetString is a stupid, dirty hack to try to convince Influx to not |
| // give back data that's outside of the range in a WHERE clause. It doesn't |
| // work, but it helps. |
| // (https://github.com/influxdata/influxdb/issues/8010) |
| func (c *TrafficStatsConfig) OffsetString() string { |
| iSecs, err := DurationLiteralToSeconds(c.Interval) |
| if err != nil { |
| // TODO: Don't log in library function - return error if there is one. |
| log.Errorf("Parsing duration literal: %v", err) |
| return "0s" |
| } |
| return fmt.Sprintf("%ds", int64(c.Start.Sub(time.Unix(0, 0))/time.Second)%iSecs) |
| } |
| |
| // TrafficDSStatsResponse represents a response from the |
| // /deliveryservice_stats "Traffic Stats" endpoints. |
| type TrafficDSStatsResponse struct { |
| // Series holds the actual data - it is NOT in general the same as a github.com/influxdata/influxdb1-client/models.Row |
| Series *TrafficStatsSeries `json:"series,omitempty"` |
| // Summary contains summary statistics of the data in Series |
| Summary *TrafficDSStatsSummary `json:"summary,omitempty"` |
| } |
| |
| // TrafficStatsResponse represents the generic response from one of the "Traffic Stats endpoints" of the |
| // Traffic Ops API, e.g. `/cache_stats`. |
| type TrafficStatsResponse struct { |
| // Series holds the actual data - it is NOT in general the same as a github.com/influxdata/influxdb1-client/models.Row |
| Series *TrafficStatsSeries `json:"series,omitempty"` |
| // Summary contains summary statistics of the data in Series |
| Summary *TrafficStatsSummary `json:"summary,omitempty"` |
| } |
| |
| // TrafficStatsSummary contains summary statistics for a data series. |
| type TrafficStatsSummary struct { |
| // Average is calculated as an arithmetic mean |
| Average float64 `json:"average"` |
| // Count is the total number of data points _except_ for any values that would appear as 'nil' |
| // in the corresponding series |
| Count uint `json:"count"` |
| FifthPercentile float64 `json:"fifthPercentile"` |
| Max float64 `json:"max"` |
| Min float64 `json:"min"` |
| NinetyEighthPercentile float64 `json:"ninetyEighthPercentile"` |
| NinetyFifthPercentile float64 `json:"ninetyFifthPercentile"` |
| } |
| |
| // LegacyTrafficDSStatsSummary contains summary statistics for a data series |
| // for Delivery Service stats. |
| // |
| // Deprecated: This uses a confusing, incorrect name for the total number of |
| // kilobytes served, so new code should use TrafficDSStatsSummary instead. |
| type LegacyTrafficDSStatsSummary struct { |
| TrafficStatsSummary |
| // TotalBytes is the total number of kilobytes served when the "metric type" requested is "kbps" |
| // (or actually just contains "kbps"). If this is not nil, TotalTransactions *should* always be |
| // nil. |
| TotalBytes *float64 `json:"totalBytes"` |
| // Totaltransactions is the total number of transactions within the requested window. Whenever |
| // the requested metric doesn't contain "kbps", it assumed to be some kind of transactions |
| // measurement. In that case, this will not be nil - otherwise it will be nil. If this not nil, |
| // TotalBytes *should* always be nil. |
| TotalTransactions *float64 `json:"totalTransactions"` |
| } |
| |
| // TrafficDSStatsSummary contains summary statistics for a data series for deliveryservice stats. |
| type TrafficDSStatsSummary struct { |
| TrafficStatsSummary |
| // TotalKiloBytes is the total number of kilobytes served when the "metric type" requested is "kbps" |
| // (or actually just contains "kbps"). If this is not nil, TotalTransactions *should* always be |
| // nil. |
| TotalKiloBytes *float64 `json:"totalKiloBytes"` |
| // Totaltransactions is the total number of transactions within the requested window. Whenever |
| // the requested metric doesn't contain "kbps", it assumed to be some kind of transactions |
| // measurement. In that case, this will not be nil - otherwise it will be nil. If this not nil, |
| // TotalBytes *should* always be nil. |
| TotalTransactions *float64 `json:"totalTransactions"` |
| } |
| |
| // TrafficStatsSeries is the actual data returned by a request to a "Traffic Stats endpoint". |
| type TrafficStatsSeries struct { |
| // Columns is a list of column names. Each "row" in Values is ordered to match up with these |
| // column names. |
| Columns []string `json:"columns"` |
| // Count is the total number of returned data points. Should be the same as len(Values) |
| Count uint `json:"count"` |
| // Name is the name of the InfluxDB database from which the data was retrieved |
| Name string `json:"name"` |
| // Tags is a set of InfluxDB tags associated with the requested database. |
| Tags map[string]string `json:"tags"` |
| // Values is an array of rows of arbitrary data that can only really be interpreted by |
| // inspecting Columns, in general. In practice, however, each element is nearly always a |
| // slice where the first element is an RFC3339 timestamp (as a string) and the second/final |
| // element is a floating point number (or nil) indicating the value at that time. |
| Values [][]interface{} `json:"values"` |
| } |
| |
| // FormatTimestamps formats the timestamps contained in the Values array as RFC3339 strings. |
| // This returns an error if the data is not in the expected format. |
| func (s TrafficStatsSeries) FormatTimestamps() error { |
| for i, v := range s.Values { |
| if len(v) != 2 { |
| return fmt.Errorf("Datapoint %d (%v) malformed", i, v) |
| } |
| |
| switch v[0].(type) { |
| case int64: |
| s.Values[i][0] = time.Unix(0, v[0].(int64)).Format(time.RFC3339) |
| case float64: |
| s.Values[i][0] = time.Unix(0, int64(v[0].(float64))).Format(time.RFC3339) |
| case json.Number: |
| val, err := v[0].(json.Number).Int64() |
| if err != nil { |
| return fmt.Errorf("Datapoint %d (%v) malformed: %v", i, v, err) |
| } |
| s.Values[i][0] = time.Unix(0, val).Format(time.RFC3339) |
| case string: |
| t, err := time.Parse(time.RFC3339, fmt.Sprintf("%v", v[0])) |
| if err != nil { |
| return fmt.Errorf("error converting timestamp %s into RFC3339 format: %v", v[0], err) |
| } |
| s.Values[i][0] = t.Format(time.RFC3339) |
| default: |
| return fmt.Errorf("Invalid type %T for datapoint %d (%v)", v[0], i, v) |
| } |
| } |
| return nil |
| } |
| |
| // MessagesToString converts a set of messages from an InfluxDB node into a |
| // single, print-able string. |
| func MessagesToString(msgs []influx.Message) string { |
| if msgs == nil || len(msgs) == 0 { |
| return "" |
| } |
| |
| b := strings.Builder{} |
| b.Write([]byte("Messages: [")) |
| for _, m := range msgs { |
| b.WriteString(m.Level) |
| b.WriteRune(':') |
| b.WriteString(m.Text) |
| b.Write([]byte(", ")) |
| } |
| b.WriteRune(']') |
| return b.String() |
| } |
| |
| // TrafficStatsCDNStats contains summary statistics for a given CDN. |
| type TrafficStatsCDNStats struct { |
| Bandwidth *float64 `json:"bandwidth"` |
| Capacity *float64 `json:"capacity"` |
| CDN string `json:"cdn"` |
| Connnections *float64 `json:"connections"` |
| } |
| |
| // TrafficStatsTotalStats contains summary statistics across CDNs. |
| // |
| // This differs from TrafficStatsCDNStats in that it omits Capacity. |
| type TrafficStatsTotalStats struct { |
| Bandwidth *float64 `json:"bandwidth"` |
| CDN string `json:"cdn"` |
| Connnections *float64 `json:"connections"` |
| } |
| |
| // TrafficStatsCDNStatsResponse contains response for getting current stats. |
| type TrafficStatsCDNStatsResponse struct { |
| Response []TrafficStatsCDNsStats `json:"response"` |
| Alerts |
| } |
| |
| // CurrentStatsResponse is the type of a response from Traffic Ops |
| // to a request to its /current_stats endpoint. |
| type CurrentStatsResponse = TrafficStatsCDNStatsResponse |
| |
| // TrafficStatsCDNsStats contains a list of CDN summary statistics. |
| type TrafficStatsCDNsStats struct { |
| Stats []TrafficStatsCDNStats `json:"currentStats"` |
| } |