blob: 31b0b172258aa812d7a5d6f39efc23ad88769d34 [file] [log] [blame]
/*
* 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.
*/
package datareq
import (
"math"
"runtime"
"sort"
"time"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-util"
"github.com/apache/trafficcontrol/traffic_monitor/config"
"github.com/apache/trafficcontrol/traffic_monitor/peer"
"github.com/apache/trafficcontrol/traffic_monitor/threadsafe"
"github.com/json-iterator/go"
)
type JSONStats struct {
Stats Stats `json:"stats"`
}
// Stats contains statistics data about this running app. Designed to be returned via an API endpoint.
type Stats struct {
MaxMemoryMB uint64 `json:"Max Memory (MB),string"`
GitRevision string `json:"git-revision"`
ErrorCount uint64 `json:"Error Count,string"`
Uptime uint64 `json:"uptime,string"`
FreeMemoryMB uint64 `json:"Free Memory (MB),string"`
TotalMemoryMB uint64 `json:"Total Memory (MB),string"`
Version string `json:"version"`
DeployDir string `json:"deploy-dir"`
FetchCount uint64 `json:"Fetch Count,string"`
QueryIntervalDelta int `json:"Query Interval Delta,string"`
IterationCount uint64 `json:"Iteration Count,string"`
Name string `json:"name"`
BuildTimestamp string `json:"buildTimestamp"`
QueryIntervalTarget int `json:"Query Interval Target,string"`
QueryIntervalActual int `json:"Query Interval Actual,string"`
SlowestCache string `json:"Slowest Cache"`
LastQueryInterval int `json:"Last Query Interval,string"`
Microthreads int `json:"Goroutines"`
LastGC string `json:"Last Garbage Collection"`
MemAllocBytes uint64 `json:"Memory Bytes Allocated"`
MemTotalBytes uint64 `json:"Total Bytes Allocated"`
MemSysBytes uint64 `json:"System Bytes Allocated"`
OldestPolledPeer string `json:"Oldest Polled Peer"`
OldestPolledPeerMs int64 `json:"Oldest Polled Peer Time (ms)"`
QueryInterval95thPercentile int64 `json:"Query Interval 95th Percentile (ms)"`
GCCPUFraction float64 `json:"gc-cpu-fraction"`
}
func srvStats(staticAppData config.StaticAppData, healthPollInterval time.Duration, lastHealthDurations threadsafe.DurationMap, fetchCount threadsafe.Uint, healthIteration threadsafe.Uint, errorCount threadsafe.Uint, peerStates peer.CRStatesPeersThreadsafe) ([]byte, error) {
return getStats(staticAppData, healthPollInterval, lastHealthDurations.Get(), fetchCount.Get(), healthIteration.Get(), errorCount.Get(), peerStates)
}
func getStats(staticAppData config.StaticAppData, pollingInterval time.Duration, lastHealthTimes map[tc.CacheName]time.Duration, fetchCount uint64, healthIteration uint64, errorCount uint64, peerStates peer.CRStatesPeersThreadsafe) ([]byte, error) {
longestPollCache, longestPollTime := getLongestPoll(lastHealthTimes)
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
var s Stats
s.MaxMemoryMB = memStats.TotalAlloc / (1024 * 1024)
s.GitRevision = staticAppData.GitRevision
s.ErrorCount = errorCount
s.Uptime = uint64(time.Since(staticAppData.StartTime) / time.Second)
s.FreeMemoryMB = staticAppData.FreeMemoryMB
s.TotalMemoryMB = memStats.Alloc / (1024 * 1024) // TODO rename to "used memory" if/when nothing is using the JSON entry
s.Version = staticAppData.Version
s.DeployDir = staticAppData.WorkingDir
s.FetchCount = fetchCount
s.SlowestCache = string(longestPollCache)
s.IterationCount = healthIteration
s.Name = staticAppData.Name
s.BuildTimestamp = staticAppData.BuildTimestamp
s.QueryIntervalTarget = int(pollingInterval / time.Millisecond)
s.QueryIntervalActual = int(longestPollTime / time.Millisecond)
s.QueryIntervalDelta = s.QueryIntervalActual - s.QueryIntervalTarget
s.LastQueryInterval = int(math.Max(float64(s.QueryIntervalActual), float64(s.QueryIntervalTarget)))
s.Microthreads = runtime.NumGoroutine()
s.LastGC = time.Unix(0, int64(memStats.LastGC)).String()
s.MemAllocBytes = memStats.Alloc
s.MemTotalBytes = memStats.TotalAlloc
s.MemSysBytes = memStats.Sys
s.GCCPUFraction = memStats.GCCPUFraction
oldestPolledPeer, oldestPolledPeerTime := oldestPeerPollTime(peerStates.GetQueryTimes(), peerStates.GetPeersOnline())
s.OldestPolledPeer = string(oldestPolledPeer)
s.OldestPolledPeerMs = time.Now().Sub((oldestPolledPeerTime)).Nanoseconds() / util.MSPerNS
s.QueryInterval95thPercentile = getCacheTimePercentile(lastHealthTimes, 0.95).Nanoseconds() / util.MSPerNS
json := jsoniter.ConfigDefault
return json.Marshal(JSONStats{Stats: s})
}
func getLongestPoll(lastHealthTimes map[tc.CacheName]time.Duration) (tc.CacheName, time.Duration) {
var longestCache tc.CacheName
var longestTime time.Duration
for cache, time := range lastHealthTimes {
if time > longestTime {
longestTime = time
longestCache = cache
}
}
return longestCache, longestTime
}
type Durations []time.Duration
func (s Durations) Len() int {
return len(s)
}
func (s Durations) Less(i, j int) bool {
return s[i] < s[j]
}
func (s Durations) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// getCacheTimePercentile returns the given percentile of cache result times. The `percentile` should be a decimal percent, for example, for the 95th percentile pass 0.95
func getCacheTimePercentile(lastHealthTimes map[tc.CacheName]time.Duration, percentile float64) time.Duration {
times := make([]time.Duration, 0, len(lastHealthTimes))
for _, t := range lastHealthTimes {
times = append(times, t)
}
sort.Sort(Durations(times))
n := int(float64(len(lastHealthTimes)) * percentile)
return times[n]
}
func oldestPeerPollTime(peerTimes map[tc.TrafficMonitorName]time.Time, peerOnline map[tc.TrafficMonitorName]bool) (tc.TrafficMonitorName, time.Time) {
now := time.Now()
oldestTime := now
oldestPeer := tc.TrafficMonitorName("")
for p, t := range peerTimes {
if !peerOnline[p] {
continue
}
if oldestTime.After(t) {
oldestTime = t
oldestPeer = p
}
}
if oldestTime == now {
oldestTime = time.Time{}
}
return oldestPeer, oldestTime
}