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