blob: 204af6c408bc12a7ceeaec200c41b4d91e51ea6b [file] [log] [blame]
package cache
/*
* 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.
*/
// stats_type_astats_dsnames is a Stat format similar to the default Astats, but with Fully Qualified Domain Names replaced with Delivery Service names (xml_id)
// That is, stat names are of the form: `"plugin.remap_stats.delivery-service-name.stat-name"`
import (
"fmt"
"strconv"
"strings"
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/traffic_monitor/dsdata"
"github.com/apache/trafficcontrol/traffic_monitor/todata"
)
func init() {
AddStatsType("astats-dsnames", astatsParse, astatsdsnamesPrecompute)
}
func astatsdsnamesPrecompute(cache tc.CacheName, toData todata.TOData, rawStats map[string]interface{}, system AstatsSystem) PrecomputedData {
stats := map[tc.DeliveryServiceName]*AStat{}
precomputed := PrecomputedData{}
var err error
if precomputed.OutBytes, err = astatsdsnamesOutBytes(system.ProcNetDev, system.InfName); err != nil {
precomputed.OutBytes = 0
log.Errorf("astatsdsnamesPrecompute %s handle precomputing outbytes '%v'\n", cache, err)
}
kbpsInMbps := int64(1000)
precomputed.MaxKbps = int64(system.InfSpeed) * kbpsInMbps
for stat, value := range rawStats {
var err error
stats, err = astatsdsnamesProcessStat(cache, stats, toData, stat, value)
if err != nil && err != dsdata.ErrNotProcessedStat {
log.Infof("precomputing cache %v stat %v value %v error %v", cache, stat, value, err)
precomputed.Errors = append(precomputed.Errors, err)
}
}
precomputed.DeliveryServiceStats = stats
return precomputed
}
// astatsdsnamesProcessStat and its subsidiary functions act as a State Machine, flowing the stat thru states for each "." component of the stat name
func astatsdsnamesProcessStat(server tc.CacheName, stats map[tc.DeliveryServiceName]*AStat, toData todata.TOData, stat string, value interface{}) (map[tc.DeliveryServiceName]*AStat, error) {
parts := strings.Split(stat, ".")
if len(parts) < 1 {
return stats, fmt.Errorf("stat has no initial part")
}
switch parts[0] {
case "plugin":
return astatsdsnamesProcessStatPlugin(server, stats, toData, stat, parts[1:], value)
case "proxy":
return stats, dsdata.ErrNotProcessedStat
case "server":
return stats, dsdata.ErrNotProcessedStat
default:
return stats, fmt.Errorf("stat '%s' has unknown initial part '%s'", stat, parts[0])
}
}
func astatsdsnamesProcessStatPlugin(server tc.CacheName, stats map[tc.DeliveryServiceName]*AStat, toData todata.TOData, stat string, statParts []string, value interface{}) (map[tc.DeliveryServiceName]*AStat, error) {
if len(statParts) < 1 {
return stats, fmt.Errorf("stat has no plugin part")
}
switch statParts[0] {
case "remap_stats":
return astatsdsnamesProcessStatPluginRemapStats(server, stats, toData, stat, statParts[1:], value)
default:
return stats, fmt.Errorf("stat has unknown plugin part '%s'", statParts[0])
}
}
func astatsdsnamesProcessStatPluginRemapStats(server tc.CacheName, stats map[tc.DeliveryServiceName]*AStat, toData todata.TOData, stat string, statParts []string, value interface{}) (map[tc.DeliveryServiceName]*AStat, error) {
if len(statParts) < 3 {
return stats, fmt.Errorf("stat has no remap_stats deliveryservice and name parts")
}
ds := tc.DeliveryServiceName(statParts[0])
statName := statParts[len(statParts)-1]
if _, ok := toData.DeliveryServiceTypes[ds]; !ok {
return stats, fmt.Errorf("no delivery service match for name '%v' stat '%v'\n", ds, statName)
}
dsStat, ok := stats[ds]
if !ok {
dsStat = &AStat{}
stats[ds] = dsStat
}
if err := astatsdstypesAddCacheStat(dsStat, statName, value); err != nil {
return stats, err
}
stats[ds] = dsStat
return stats, nil
}
// astatsdsnamesOutBytes takes the proc.net.dev string, and the interface name, and returns the bytes field
// NOTE this is superficially duplicated from astatsOutBytes, but they are conceptually different, because the `astats` format changing should not necessarily affect the `astats-dstypes` format. The MUST be kept separate, and code between them MUST NOT be de-duplicated.
func astatsdsnamesOutBytes(procNetDev, iface string) (int64, error) {
if procNetDev == "" {
return 0, fmt.Errorf("procNetDev empty")
}
if iface == "" {
return 0, fmt.Errorf("iface empty")
}
ifacePos := strings.Index(procNetDev, iface)
if ifacePos == -1 {
return 0, fmt.Errorf("interface '%s' not found in proc.net.dev '%s'", iface, procNetDev)
}
procNetDevIfaceBytes := procNetDev[ifacePos+len(iface)+1:]
procNetDevIfaceBytesArr := strings.Fields(procNetDevIfaceBytes) // TODO test
if len(procNetDevIfaceBytesArr) < 10 {
return 0, fmt.Errorf("proc.net.dev iface '%v' unknown format '%s'", iface, procNetDev)
}
procNetDevIfaceBytes = procNetDevIfaceBytesArr[8]
return strconv.ParseInt(procNetDevIfaceBytes, 10, 64)
}
// astatsdstypesAddCacheStat adds the given stat to the existing stat. Note this adds, it doesn't overwrite. Numbers are summed, strings are concatenated.
func astatsdstypesAddCacheStat(stat *AStat, name string, val interface{}) error {
// TODO make this less duplicate code somehow.
// NOTE this is superficially duplicated from astatsAddCacheStat, but they are conceptually different, because the `astats` format changing should not necessarily affect the `astats-dstypes` format. The MUST be kept separate, and code between them MUST NOT be de-duplicated.
switch name {
case "status_2xx":
v, ok := val.(float64)
if !ok {
return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
}
stat.Status2xx += uint64(v)
case "status_3xx":
v, ok := val.(float64)
if !ok {
return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
}
stat.Status3xx += uint64(v)
case "status_4xx":
v, ok := val.(float64)
if !ok {
return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
}
stat.Status4xx += uint64(v)
case "status_5xx":
v, ok := val.(float64)
if !ok {
return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
}
stat.Status5xx += uint64(v)
case "out_bytes":
v, ok := val.(float64)
if !ok {
return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
}
stat.OutBytes += uint64(v)
case "in_bytes":
v, ok := val.(float64)
if !ok {
return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
}
stat.InBytes += uint64(v)
case "status_unknown":
return dsdata.ErrNotProcessedStat
default:
return fmt.Errorf("unknown stat '%s'", name)
}
return nil
}