/
cachesstats.go
232 lines (212 loc) · 7.56 KB
/
cachesstats.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package cachesstats
/*
* 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 (
"database/sql"
"errors"
"net/http"
"strconv"
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-util"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/monitorhlp"
)
const ATSCurrentConnectionsStat = "ats.proxy.process.http.current_client_connections"
func Get(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
api.RespWriter(w, r, inf.Tx.Tx)(getCachesStats(inf.Tx.Tx))
}
func getCachesStats(tx *sql.Tx) ([]CacheData, error) {
monitors, err := monitorhlp.GetURLs(tx)
if err != nil {
return nil, errors.New("getting monitors: " + err.Error())
}
client, err := monitorhlp.GetClient(tx)
if err != nil {
return nil, errors.New("getting monitor client: " + err.Error())
}
cacheData, err := getCacheData(tx)
if err != nil {
return nil, errors.New("getting cache data: " + err.Error())
}
for cdn, monitorFQDNs := range monitors {
if len(monitorFQDNs) == 0 {
log.Warnln("getCachesStats: cdn '" + string(cdn) + "' has no online monitors, skipping!")
continue
}
success := false
errs := []error{}
for _, monitorFQDN := range monitorFQDNs {
crStates, err := monitorhlp.GetCRStates(monitorFQDN, client)
if err != nil {
errs = append(errs, errors.New("getting CRStates for CDN '"+string(cdn)+"' monitor '"+monitorFQDN+"': "+err.Error()))
continue
}
var cacheStats tc.Stats
var url string
stats := []string{ATSCurrentConnectionsStat, tc.StatNameBandwidth}
cacheStats, url, err = monitorhlp.GetCacheStats(monitorFQDN, client, stats)
if err != nil {
legacyCacheStats, legacyUrl, err := monitorhlp.GetLegacyCacheStats(monitorFQDN, client, stats)
if err != nil {
errs = append(errs, errors.New("getting CacheStats for CDN '"+string(cdn)+"' monitor '"+monitorFQDN+"': "+err.Error()))
continue
}
url = legacyUrl
cacheStats = monitorhlp.UpgradeLegacyStats(legacyCacheStats)
}
cacheData = addHealth(cacheData, crStates)
cacheData = addStats(cacheData, cacheStats, url)
success = true
break
}
if !success {
return nil, errors.New("getting cache stats from all monitors failed for cdn '" + string(cdn) + "': " + util.JoinErrs(errs).Error())
}
// if we succeeded, log the monitor failures but don't return them
for _, err := range errs {
log.Errorln(err.Error())
}
}
cacheData = addTotals(cacheData)
return cacheData, nil
}
// addTotals sums each cachegroup, and adds the sum to an object with the cache-specific fields set to "ALL", and the cachegroup. It then sums all cachegroups, and adds the total to an object with all fields set to "ALL".
// TODO in the next API version, add totals in their own JSON objects, not amidst the cachegroup keys.
func addTotals(data []CacheData) []CacheData {
all := "ALL"
cachegroups := map[tc.CacheGroupName]CacheData{}
total := CacheData{Profile: all, Status: all, Healthy: true, HostName: tc.CacheName(all), CacheGroup: tc.CacheGroupName(all)}
for _, d := range data {
cg := cachegroups[tc.CacheGroupName(d.CacheGroup)]
cg.CacheGroup = d.CacheGroup
cg.Connections += d.Connections
cg.KBPS += d.KBPS
cachegroups[tc.CacheGroupName(d.CacheGroup)] = cg
total.Connections += d.Connections
total.KBPS += d.KBPS
}
for _, cg := range cachegroups {
cg.Profile = all
cg.Status = all
cg.Healthy = true
cg.HostName = tc.CacheName(all)
data = append(data, cg)
}
data = append(data, total)
return data
}
func addStats(cacheData []CacheData, stats tc.Stats, url string) []CacheData {
var err error
if stats.Caches == nil {
return cacheData // TODO warn?
}
for i, cache := range cacheData {
stat, ok := stats.Caches[string(cache.HostName)]
if !ok {
continue
}
bandwidth, ok := stat.Stats[tc.StatNameBandwidth]
if ok && len(bandwidth) > 0 {
if kbps, ok := bandwidth[0].Val.(string); !ok {
log.Warnf("bandwidth %v of cache %s from url %s couldn't be converted into string", bandwidth[0].Val, string(cache.HostName), url)
} else {
cache.KBPS, err = strconv.ParseUint(kbps, 10, 64)
if err != nil {
log.Warnf("'bandwidth' stat %v of cache %s from url %s couldn't be converted into uint64", kbps, string(cache.HostName), url)
}
}
}
connections, ok := stat.Stats[ATSCurrentConnectionsStat]
if ok && len(connections) > 0 {
if conn, ok := connections[0].Val.(string); !ok {
log.Warnf("'connections' stat %v of cache %s from url %s couldn't be converted into string", connections[0].Val, string(cache.HostName), url)
} else {
cache.Connections, err = strconv.ParseUint(conn, 10, 64)
if err != nil {
log.Warnf("'connections' stat %v of cache %s from url %s couldn't be converted into uint64", conn, string(cache.HostName), url)
}
}
}
cacheData[i] = cache
}
return cacheData
}
func addHealth(cacheData []CacheData, crStates tc.CRStates) []CacheData {
if crStates.Caches == nil {
return cacheData // TODO warn?
}
for i, cache := range cacheData {
crsCache, ok := crStates.Caches[cache.HostName]
if !ok {
continue
}
cache.Healthy = crsCache.IsAvailable
cacheData[i] = cache
}
return cacheData
}
type CacheData struct {
HostName tc.CacheName `json:"hostname"`
CacheGroup tc.CacheGroupName `json:"cachegroup"`
Status string `json:"status"`
Profile string `json:"profile"`
IP *string `json:"ip"`
Healthy bool `json:"healthy"`
KBPS uint64 `json:"kbps"`
Connections uint64 `json:"connections"`
}
// getCacheData gets the cache data from the servers table. Note this only gets from the database, and thus does not set the Healthy member.
func getCacheData(tx *sql.Tx) ([]CacheData, error) {
qry := `
SELECT
s.host_name,
cg.name as cachegroup,
st.name as status,
p.name as profile,
(select address from ip_address where s.id = ip_address.server and service_address = true AND family(address) = 4) as ip
FROM
server s
JOIN cachegroup cg ON s.cachegroup = cg.id
JOIN status st ON s.status = st.id
JOIN profile p ON s.profile = p.id
WHERE
p.name LIKE '` + tc.CacheTypeEdge.String() + `%' OR p.name LIKE '` + tc.CacheTypeMid.String() + `%'
`
rows, err := tx.Query(qry)
if err != nil {
return nil, errors.New("querying cache data: " + err.Error())
}
defer rows.Close()
data := []CacheData{}
for rows.Next() {
d := CacheData{}
if err := rows.Scan(&d.HostName, &d.CacheGroup, &d.Status, &d.Profile, &d.IP); err != nil {
return nil, errors.New("scanning cache data: " + err.Error())
}
data = append(data, d)
}
return data, nil
}