| /* |
| * 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 |
| } |