blob: 5c92d8b84bfdce8822b1c506bdd2d38f5b283e8b [file] [log] [blame]
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"`
}