1 /*
2 * Idea taken from squid_stat by StarSoft Ltd.
3 * (C) 2006 Oleg V. Palij <o.palij@gmail.com>
4 * Released under the GNU GPL, see the COPYING file in the source distribution for its full text.
5 */
6
7 //atoll,atoi,sort
8 #include <algorithm>
9 //stringstream
10 #include <sstream>
11 // for "compactsameurls" option
12 #include <map>
13 #include <arpa/inet.h>
14 #include <iostream>
15
16 #include "sqconn.hpp"
17 #include "sqstat.hpp"
18 #include "Base64.hpp"
19 #include "Utils.hpp"
20 #include "strings.hpp"
21 #include "options.hpp"
22
23 namespace sqtop {
24
25 using std::string;
26 using std::vector;
27 using std::endl;
28
sqstat()29 sqstat::sqstat() {
30 lastruntime = 0;
31 }
32
CompareURLs(Uri_Stats a,Uri_Stats b)33 /* static */ bool sqstat::CompareURLs(Uri_Stats a, Uri_Stats b) {
34 return a.size > b.size;
35 }
36
CompareIP(SQUID_Connection a,SQUID_Connection b)37 /* static */ bool sqstat::CompareIP(SQUID_Connection a, SQUID_Connection b) {
38 unsigned long ip1, ip2;
39 struct sockaddr_in n;
40 inet_aton(a.peer.c_str(), &n.sin_addr);
41 ip1 = ntohl(n.sin_addr.s_addr);
42 inet_aton(b.peer.c_str(), &n.sin_addr);
43 ip2 = ntohl(n.sin_addr.s_addr);
44 return ip1 < ip2;
45 }
46
47 /* for std::find_if */
ConnByPeer(SQUID_Connection conn,string Host)48 /* static */ bool sqstat::ConnByPeer(SQUID_Connection conn, string Host) {
49 return conn.peer == Host;
50 }
51
52 /* for std::find_if */
StatByID(Uri_Stats stat,string id)53 /* static */ bool sqstat::StatByID(Uri_Stats stat, string id) {
54 return stat.id == id;
55 }
56
CompareSIZE(SQUID_Connection a,SQUID_Connection b)57 /* static */ bool sqstat::CompareSIZE(SQUID_Connection a, SQUID_Connection b) {
58 return a.sum_size > b.sum_size;
59 }
60
CompareTIME(SQUID_Connection a,SQUID_Connection b)61 /* static */ bool sqstat::CompareTIME(SQUID_Connection a, SQUID_Connection b) {
62 return a.max_etime > b.max_etime;
63 }
64
CompareAVSPEED(SQUID_Connection a,SQUID_Connection b)65 /* static */ bool sqstat::CompareAVSPEED(SQUID_Connection a, SQUID_Connection b) {
66 return a.av_speed > b.av_speed;
67 }
68
CompareCURRSPEED(SQUID_Connection a,SQUID_Connection b)69 /* static */ bool sqstat::CompareCURRSPEED(SQUID_Connection a, SQUID_Connection b) {
70 return a.curr_speed > b.curr_speed;
71 }
72
CompactSameUrls(vector<SQUID_Connection> & sqconns)73 /* static */ void sqstat::CompactSameUrls(vector<SQUID_Connection>& sqconns) {
74 for (vector<SQUID_Connection>::iterator it = sqconns.begin(); it != sqconns.end(); ++it) {
75 std::map<string, Uri_Stats> urls;
76
77 for (vector<Uri_Stats>::iterator itu = it->stats.begin(); itu != it->stats.end(); ++itu) {
78 string url = itu->uri;
79 // TODO: check if username is the same ?
80 if (urls.find(url) == urls.end()) {
81 urls[url] = *itu;
82 }
83 else {
84 urls[url].count += 1;
85 urls[url].size += itu->size;
86 urls[url].etime += itu->etime;
87 // TODO: check this
88 if ((urls[url].size !=0) && (urls[url].etime != 0))
89 urls[url].av_speed = urls[url].size/urls[url].etime;
90 }
91 }
92
93 it->stats.clear();
94 for (std::map<string, Uri_Stats>::iterator itm=urls.begin(); itm!=urls.end(); itm++) {
95 it->stats.push_back(itm->second);
96 }
97 sort(it->stats.begin(), it->stats.end(), CompareURLs);
98 }
99 }
100
HeadFormat(Options * pOpts,int active_conn,int active_ips,long av_speed)101 /* static */ string sqstat::HeadFormat(Options* pOpts, int active_conn, int active_ips, long av_speed) {
102 std::stringstream result;
103 if ((pOpts->Hosts.size() == 0) && (pOpts->Users.size() == 0)) {
104 result << endl << "Active connections: " << active_conn;
105 result << ", active hosts: " << active_ips;
106 if (pOpts->zero || (av_speed > 103))
107 result << ", average speed: " << Utils::ConvertSpeed(av_speed);
108 result << endl;
109 }
110 return result.str();
111 }
112
ConnFormat(Options * pOpts,SQUID_Connection & scon)113 /* static */ string sqstat::ConnFormat(Options* pOpts, SQUID_Connection& scon) {
114 std::stringstream result;
115
116 result << " Host: ";
117 #ifdef WITH_RESOLVER
118 string resolved;
119 if (pOpts->dns_resolution) {
120 string tmp = scon.hostname;
121 if (pOpts->strip_domain) {
122 pOpts->pResolver->StripDomain(tmp);
123 }
124 switch (pOpts->resolve_mode) {
125 case Options::SHOW_NAME:
126 resolved = tmp;
127 break;
128 case Options::SHOW_IP:
129 resolved = scon.peer;
130 break;
131 case Options::SHOW_BOTH:
132 if (!tmp.compare(scon.peer)) {
133 resolved = scon.peer;
134 } else {
135 resolved = tmp + " [" + scon.peer + "]";
136 }
137 break;
138 };
139 } else {
140 resolved = scon.peer;
141 }
142 result << resolved;
143 #else
144 result << scon.peer;
145 #endif
146 if (!scon.usernames.empty()) {
147 string head;
148 if (scon.usernames.size() == 1)
149 head = "User: ";
150 else
151 head = "Users: ";
152 result << "; " + head << Utils::UsernamesToStr(scon.usernames);
153 }
154
155 string condetail="";
156 if (pOpts->full || pOpts->brief)
157 condetail += "sessions: " + Utils::itos(scon.stats.size()) + ", ";
158 if (pOpts->zero || (scon.sum_size > 1024))
159 condetail += "size: " + Utils::ConvertSize(scon.sum_size) + ", ";
160 if (pOpts->zero || (scon.curr_speed > 103) || (scon.av_speed > 103)) {
161 condetail += SpeedsFormat(pOpts->speed_mode, scon.av_speed, scon.curr_speed) + ", ";
162 }
163 if (pOpts->full && (pOpts->zero || scon.max_etime > 0))
164 condetail += "max time: " + Utils::ConvertTime(scon.max_etime) + ", ";
165 if (condetail.size() > 2) {
166 condetail.resize(condetail.size()-2);
167 result << " (" << condetail << ")";
168 }
169 return result.str();
170 }
171
SpeedsFormat(Options::SPEED_MODE mode,long av_speed,long curr_speed)172 string sqstat::SpeedsFormat(Options::SPEED_MODE mode, long av_speed, long curr_speed) {
173 std::stringstream result;
174 std::pair <string, string> av_speed_pair;
175 std::pair <string, string> curr_speed_pair;
176 av_speed_pair = Utils::ConvertSpeedPair(av_speed);
177 /*if ((curr_speed == 0) && (mode == Options::SPEED_MIXED)) {
178 mode = Options::SPEED_AVERAGE;
179 }*/
180 switch (mode) {
181 case Options::SPEED_CURRENT:
182 curr_speed_pair = Utils::ConvertSpeedPair(curr_speed);
183 result << "current speed: " << curr_speed_pair.first << curr_speed_pair.second;
184 break;
185 case Options::SPEED_MIXED:
186 if ((curr_speed != av_speed) && (curr_speed > 103)) {
187 std::pair <string, string> curr_speed_pair;
188 curr_speed_pair = Utils::ConvertSpeedPair(curr_speed);
189 result << "current/average speed: ";
190 if (av_speed_pair.second == curr_speed_pair.second) {
191 result << curr_speed_pair.first << "/" << av_speed_pair.first << " " << av_speed_pair.second;
192 } else {
193 result << curr_speed_pair.first << curr_speed_pair.second << " / " << av_speed_pair.first << av_speed_pair.second;
194 }
195 break;
196 }
197 case Options::SPEED_AVERAGE:
198 result << "average speed: " + av_speed_pair.first + " " + av_speed_pair.second;
199 break;
200 };
201 return result.str();
202 }
203
StatFormat(Options * pOpts,SQUID_Connection & scon,Uri_Stats & ustat)204 /* static */ string sqstat::StatFormat(Options* pOpts, SQUID_Connection& scon, Uri_Stats& ustat) {
205 std::stringstream result;
206 result << " " << ustat.uri;
207 string udetail = "";
208 if (ustat.count > 1) {
209 udetail += "count: " + Utils::itos(ustat.count) + ", ";
210 }
211 if (pOpts->detail) {
212 if (pOpts->zero || (ustat.size > 1024))
213 udetail += "size: " + Utils::ConvertSize(ustat.size) + ", ";
214 if (pOpts->full && ((pOpts->zero || (ustat.etime > 0))))
215 udetail += "time: " + Utils::ConvertTime(ustat.etime) + ", ";
216 if (scon.usernames.size() > 1)
217 udetail += "user: " + ustat.username + ", ";
218 if (pOpts->zero || (ustat.av_speed > 103) || (ustat.curr_speed > 103))
219 udetail += SpeedsFormat(pOpts->speed_mode, ustat.av_speed, ustat.curr_speed) + ", ";
220 if (pOpts->full && (pOpts->zero || (ustat.delay_pool != 0)))
221 udetail += "delay_pool: " + Utils::itos(ustat.delay_pool) + ", ";
222 }
223 if (udetail.size() > 2) {
224 udetail.resize(udetail.size()-2);
225 result << " (" << udetail << ")";
226 }
227 return result.str();
228 }
229
FindUriStatsById(vector<SQUID_Connection> conns,string id)230 Uri_Stats sqstat::FindUriStatsById(vector<SQUID_Connection> conns, string id) {
231 for (vector<SQUID_Connection>::iterator it = conns.begin(); it != conns.end(); ++it) {
232 vector<Uri_Stats>::iterator itu = find_if(it->stats.begin(), it->stats.end(), bind2nd(ptr_fun(StatByID), id));
233 if (itu != it->stats.end())
234 return *itu;
235 }
236 Uri_Stats newStats;
237 return newStats;
238 }
239
240 /*string get_ip() {
241 char str[100];
242 struct hostent *he;
243 if (gethostname(str,100)!=0) {
244 throw "Failed to gethostname: " + string(strerror(errno));
245 }
246 he=gethostbyname(str);
247 if (he==NULL) {
248 throw "Failed to gethostbyname: " + Utils::itos(h_errno);
249 }
250 return string(inet_ntoa(*(struct in_addr *) he->h_addr));
251 }*/
252
FormatChanged(string line)253 void sqstat::FormatChanged(string line) {
254 std::stringstream result;
255 result << "Warning!!! Please send bug report.";
256 result << " active_requests format changed - \'" << line << "\'.";
257 result << " " << squid_version << ".";
258 result << " " << PACKAGE_NAME << "-" << VERSION;
259 throw sqstatException(result.str(), FORMAT_CHANGED);
260 }
261
262 #ifdef WITH_RESOLVER
DoResolve(Options * pOpts,string peer)263 string sqstat::DoResolve(Options* pOpts, string peer) {
264 string resolved;
265 if (pOpts->dns_resolution) {
266 resolved = pOpts->pResolver->Resolve(peer);
267 } else {
268 resolved = peer;
269 }
270 return resolved;
271 }
272 #endif
273
GetInfo(Options * pOpts)274 vector<SQUID_Connection> sqstat::GetInfo(Options* pOpts) {
275 sqconn con;
276
277 string temp_str;
278
279 active_conn = 0;
280
281 long long esize;
282 long etime;
283
284 int n=0, delay_pool;
285
286 vector<SQUID_Connection>::iterator Conn_it; // pointer to current peer
287 vector<Uri_Stats>::iterator Stat_it; // pointer to current stat
288 Uri_Stats newStats;
289
290 try {
291 con.open(pOpts->host, pOpts->port);
292 }
293 catch(sqconnException &e) {
294 std::stringstream error;
295 error << e.what() << " while connecting to " << pOpts->host << ":" << pOpts->port;
296 throw sqstatException(error.str(), FAILED_TO_CONNECT);
297 }
298
299 connections.clear();
300
301 time_t timenow = 0;
302 time_t timediff = 0;
303
304 try {
305 string request = "GET cache_object://localhost/active_requests HTTP/1.0\n";
306 if (!pOpts->pass.empty()) {
307 string encoded = Base64::Encode("admin:" + pOpts->pass);
308 request += "Authorization: Basic " + encoded + "\n";
309 }
310 con << request;
311 Uri_Stats oldStats;
312 while ((con >> temp_str) != 0) {
313 if (connections.size()==0) {
314 if (n==0) {
315 if (temp_str != "HTTP/1.0 200 OK" && temp_str != "HTTP/1.1 200 OK") {
316 std::stringstream error;
317 error << "Access to squid statistic denied: " << temp_str;
318 /*string ip;
319 try {
320 ip = get_ip();
321 }
322 catch (string) {
323 ip = "<your_host_ip>";
324 }*/
325 /*error << "You must enable access to squid statistic in squid.conf by adding strings like:" << endl << endl;
326 error << "\tacl adminhost src <admin_host_ip_here>/255.255.255.255" << endl;
327 error << "\thttp_access allow manager adminhost" << endl;
328 error << "\thttp_access deny manager";*/
329 throw sqstatException(error.str(), ACCESS_DENIED);
330 } else {
331 n=1;
332 timenow = time(NULL);
333 timediff = timenow - lastruntime;
334 continue;
335 }
336 }
337 }
338
339 vector<string> result;
340 if (temp_str.substr(0,8) == "Server: ") {
341 result = Utils::SplitString(temp_str, " ");
342 if (result.size() == 2) {
343 squid_version = result[1];
344 } else { FormatChanged(temp_str); }
345 } else if (temp_str.substr(0,12) == "Connection: ") {
346 result = Utils::SplitString(temp_str, " ");
347 if (result.size() == 2) {
348 newStats.id = result[1];
349 oldStats = FindUriStatsById(oldConnections, result[1]);
350 newStats.uri = "";
351 newStats.username = "";
352 newStats.size = 0;
353 newStats.count = 0;
354 newStats.oldsize = 0;
355 newStats.etime = 0;
356 newStats.delay_pool = -1;
357 } else { FormatChanged(temp_str); }
358 } else if ((temp_str.substr(0,6) == "peer: ") or (temp_str.substr(0,8) == "remote: ")) {
359 result = Utils::SplitString(temp_str, " ");
360 if (result.size() == 2) {
361 std::pair <string, string> peer = Utils::SplitIPPort(result[1]);
362 if (!peer.first.empty()) {
363 Conn_it = std::find_if( connections.begin(), connections.end(), std::bind2nd( std::ptr_fun(ConnByPeer) , peer.first) );
364 // if it is new peer, create new SQUID_Connection
365 if (Conn_it == connections.end()) {
366 SQUID_Connection connection;
367 connection.peer = peer.first;
368 #ifdef WITH_RESOLVER
369 connection.hostname = DoResolve(pOpts, peer.first);
370 #endif
371 connections.push_back(connection);
372 Conn_it = connections.end() - 1;
373 }
374 Conn_it->stats.push_back(newStats);
375 Stat_it = Conn_it->stats.end() - 1;
376 }
377 } else { FormatChanged(temp_str); }
378 } else if (temp_str.substr(0,4) == "uri ") {
379 result = Utils::SplitString(temp_str, " ");
380 if (result.size() == 2) {
381 Stat_it->uri = result[1];
382 Stat_it->count = 1;
383 Stat_it->curr_speed = 0;
384 Stat_it->av_speed = 0;
385 } else { FormatChanged(temp_str); }
386 } else if (temp_str.substr(0,11) == "out.offset ") {
387 result = Utils::SplitString(temp_str, " ");
388 if (result.size() == 4) {
389 esize = atoll(result[3].c_str());
390 Stat_it->size = esize;
391 Stat_it->oldsize = oldStats.size;
392 Conn_it->sum_size += esize;
393 } else { FormatChanged(temp_str); }
394 } else if (temp_str.substr(0,6) == "start ") {
395 result = Utils::SplitString(temp_str, " ");
396 if (result.size() == 5) {
397 string temp = result[2].erase(0,1);
398 etime = atoi(temp.c_str());
399 Stat_it->etime = etime;
400 if (etime > Conn_it->max_etime)
401 Conn_it->max_etime = etime;
402 } else { FormatChanged(temp_str); }
403 } else if (temp_str.substr(0,11) == "delay_pool ") {
404 result = Utils::SplitString(temp_str, " ");
405 if (result.size() == 2) {
406 string temp = result[1];
407 delay_pool = atoi(temp.c_str());
408 Stat_it->delay_pool = delay_pool;
409 } else { FormatChanged(temp_str); }
410 } else if (temp_str.substr(0,9) == "username ") {
411 result = Utils::SplitString(temp_str, " ");
412 if (result.size() == 1)
413 result.push_back("-");
414 if (result.size() == 2) {
415 string username = result[1];
416 if (!(username == "-")) {
417 Utils::ToLower(username);
418 Stat_it->username = username;
419 if (!Utils::MemberOf(Conn_it->usernames, username))
420 Conn_it->usernames.push_back(username);
421 }
422 } else { FormatChanged(temp_str); }
423 }
424 }
425 }
426 catch(sqconnException &e) {
427 std::stringstream error;
428 error << e.what();
429 throw sqstatException(error.str(), UNKNOWN_ERROR);
430 }
431
432 av_speed = 0;
433 curr_speed = 0;
434 for (vector<SQUID_Connection>::iterator Conn = connections.begin(); Conn != connections.end(); ++Conn) {
435
436 sort(Conn->stats.begin(), Conn->stats.end(), CompareURLs);
437
438 active_conn += Conn->stats.size();
439
440 for (vector<Uri_Stats>::iterator Stats = Conn->stats.begin(); Stats != Conn->stats.end(); ++Stats) {
441 long stat_av_speed = 0;
442 if ((Stats->size != 0) && (Stats->etime != 0))
443 stat_av_speed = Stats->size/Stats->etime;
444 Stats->av_speed = stat_av_speed;
445 Conn->av_speed += stat_av_speed;
446 av_speed += stat_av_speed;
447 long stat_curr_speed = 0;
448 if ((Stats->size != 0) && (Stats->oldsize != 0) && (lastruntime != 0) && (Stats->size > Stats->oldsize)) {
449 if (timediff < 1) timediff = 1;
450 stat_curr_speed = (Stats->size - Stats->oldsize) / timediff;
451 /*if ((stat_curr_speed > 10000000) || (stat_curr_speed < 0)) {
452 cout << Stats->size << " " << Stats->oldsize << " " << timenow << " " << lastruntime << endl;
453 throw;
454 }*/
455 Stats->curr_speed = stat_curr_speed;
456 Conn->curr_speed += stat_curr_speed;
457 curr_speed += stat_curr_speed;
458 } /*else {
459 Conn->curr_speed += stat_av_speed;
460 curr_speed += stat_av_speed;
461 }*/
462 }
463 }
464
465 sort(connections.begin(), connections.end(), sqstat::CompareSIZE);
466
467 oldConnections = connections;
468 lastruntime = timenow;
469
470 return connections;
471 }
472
473 }
474 // vim: ai ts=3 sts=3 et sw=3 expandtab
475