1 /** @file
2 
3     Main file for the traffic_top application.
4 
5     @section license License
6 
7     Licensed to the Apache Software Foundation (ASF) under one
8     or more contributor license agreements.  See the NOTICE file
9     distributed with this work for additional information
10     regarding copyright ownership.  The ASF licenses this file
11     to you under the Apache License, Version 2.0 (the
12     "License"); you may not use this file except in compliance
13     with the License.  You may obtain a copy of the License at
14 
15     http://www.apache.org/licenses/LICENSE-2.0
16 
17     Unless required by applicable law or agreed to in writing, software
18     distributed under the License is distributed on an "AS IS" BASIS,
19     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20     See the License for the specific language governing permissions and
21     limitations under the License.
22 */
23 
24 #include "tscore/ink_config.h"
25 #include <map>
26 #include <list>
27 #include <string>
28 #include <cstring>
29 #include <iostream>
30 #include <cassert>
31 #include <cstdlib>
32 #include <unistd.h>
33 #include <getopt.h>
34 
35 // At least on solaris, the default ncurses defines macros such as
36 // clear() that break stdlibc++.
37 #define NOMACROS 1
38 #define NCURSES_NOMACROS 1
39 
40 #if defined HAVE_NCURSESW_CURSES_H
41 #include <ncursesw/curses.h>
42 #elif defined HAVE_NCURSESW_H
43 #include <ncursesw.h>
44 #elif defined HAVE_NCURSES_CURSES_H
45 #include <ncurses/curses.h>
46 #elif defined HAVE_NCURSES_H
47 #include <ncurses.h>
48 #elif defined HAVE_CURSES_H
49 #include <curses.h>
50 #else
51 #error "SysV or X/Open-compatible Curses header file required"
52 #endif
53 
54 #include "stats.h"
55 
56 #include "tscore/I_Layout.h"
57 #include "tscore/ink_args.h"
58 #include "records/I_RecProcess.h"
59 #include "RecordsConfig.h"
60 #include "tscore/runroot.h"
61 
62 using namespace std;
63 
64 #if HAS_CURL
65 char curl_error[CURL_ERROR_SIZE];
66 #endif
67 string response;
68 
69 namespace colorPair
70 {
71 const short red    = 1;
72 const short yellow = 2;
73 const short green  = 3;
74 const short blue   = 4;
75 //  const short black = 5;
76 const short grey   = 6;
77 const short cyan   = 7;
78 const short border = 8;
79 }; // namespace colorPair
80 
81 //----------------------------------------------------------------------------
82 static void
prettyPrint(const int x,const int y,const double number,const int type)83 prettyPrint(const int x, const int y, const double number, const int type)
84 {
85   char buffer[32];
86   char exp         = ' ';
87   double my_number = number;
88   short color;
89   if (number > 1000000000000LL) {
90     my_number = number / 1000000000000LL;
91     exp       = 'T';
92     color     = colorPair::red;
93   } else if (number > 1000000000) {
94     my_number = number / 1000000000;
95     exp       = 'G';
96     color     = colorPair::red;
97   } else if (number > 1000000) {
98     my_number = number / 1000000;
99     exp       = 'M';
100     color     = colorPair::yellow;
101   } else if (number > 1000) {
102     my_number = number / 1000;
103     exp       = 'K';
104     color     = colorPair::cyan;
105   } else if (my_number <= .09) {
106     color = colorPair::grey;
107   } else {
108     color = colorPair::green;
109   }
110 
111   if (type == 4 || type == 5) {
112     if (number > 90) {
113       color = colorPair::red;
114     } else if (number > 80) {
115       color = colorPair::yellow;
116     } else if (number > 50) {
117       color = colorPair::blue;
118     } else if (my_number <= .09) {
119       color = colorPair::grey;
120     } else {
121       color = colorPair::green;
122     }
123     snprintf(buffer, sizeof(buffer), "%6.1f%%%%", my_number);
124   } else {
125     snprintf(buffer, sizeof(buffer), "%6.1f%c", my_number, exp);
126   }
127   attron(COLOR_PAIR(color));
128   attron(A_BOLD);
129   mvprintw(y, x, buffer);
130   attroff(COLOR_PAIR(color));
131   attroff(A_BOLD);
132 }
133 
134 //----------------------------------------------------------------------------
135 static void
makeTable(const int x,const int y,const list<string> & items,Stats & stats)136 makeTable(const int x, const int y, const list<string> &items, Stats &stats)
137 {
138   int my_y = y;
139 
140   for (const auto &item : items) {
141     string prettyName;
142     double value = 0;
143     int type;
144 
145     stats.getStat(item, value, prettyName, type);
146     mvprintw(my_y, x, prettyName.c_str());
147     prettyPrint(x + 10, my_y++, value, type);
148   }
149 }
150 
151 //----------------------------------------------------------------------------
152 size_t
write_data(void * ptr,size_t size,size_t nmemb,void *)153 write_data(void *ptr, size_t size, size_t nmemb, void * /* stream */)
154 {
155   response.append(static_cast<char *>(ptr), size * nmemb);
156   return size * nmemb;
157 }
158 
159 //----------------------------------------------------------------------------
160 static void
response_code_page(Stats & stats)161 response_code_page(Stats &stats)
162 {
163   attron(COLOR_PAIR(colorPair::border));
164   attron(A_BOLD);
165   mvprintw(0, 0, "                              RESPONSE CODES                                   ");
166   attroff(COLOR_PAIR(colorPair::border));
167   attroff(A_BOLD);
168 
169   list<string> response1;
170   response1.push_back("100");
171   response1.push_back("101");
172   response1.push_back("1xx");
173   response1.push_back("200");
174   response1.push_back("201");
175   response1.push_back("202");
176   response1.push_back("203");
177   response1.push_back("204");
178   response1.push_back("205");
179   response1.push_back("206");
180   response1.push_back("2xx");
181   response1.push_back("300");
182   response1.push_back("301");
183   response1.push_back("302");
184   response1.push_back("303");
185   response1.push_back("304");
186   response1.push_back("305");
187   response1.push_back("307");
188   response1.push_back("3xx");
189   makeTable(0, 1, response1, stats);
190 
191   list<string> response2;
192   response2.push_back("400");
193   response2.push_back("401");
194   response2.push_back("402");
195   response2.push_back("403");
196   response2.push_back("404");
197   response2.push_back("405");
198   response2.push_back("406");
199   response2.push_back("407");
200   response2.push_back("408");
201   response2.push_back("409");
202   response2.push_back("410");
203   response2.push_back("411");
204   response2.push_back("412");
205   response2.push_back("413");
206   response2.push_back("414");
207   response2.push_back("415");
208   response2.push_back("416");
209   response2.push_back("4xx");
210   makeTable(21, 1, response2, stats);
211 
212   list<string> response3;
213   response3.push_back("500");
214   response3.push_back("501");
215   response3.push_back("502");
216   response3.push_back("503");
217   response3.push_back("504");
218   response3.push_back("505");
219   response3.push_back("5xx");
220   makeTable(42, 1, response3, stats);
221 }
222 
223 //----------------------------------------------------------------------------
224 static void
help(const string & host,const string & version)225 help(const string &host, const string &version)
226 {
227   timeout(1000);
228 
229   while (true) {
230     clear();
231     time_t now = time(nullptr);
232     struct tm nowtm;
233     char timeBuf[32];
234     localtime_r(&now, &nowtm);
235     strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", &nowtm);
236 
237     // clear();
238     attron(A_BOLD);
239     mvprintw(0, 0, "Overview:");
240     attroff(A_BOLD);
241     mvprintw(
242       1, 0,
243       "traffic_top is a top like program for Apache Traffic Server (ATS). "
244       "There is a lot of statistical information gathered by ATS. "
245       "This program tries to show some of the more important stats and gives a good overview of what the proxy server is doing. "
246       "Hopefully this can be used as a tool for diagnosing the proxy server if there are problems.");
247 
248     attron(A_BOLD);
249     mvprintw(7, 0, "Definitions:");
250     attroff(A_BOLD);
251     mvprintw(8, 0, "Fresh      => Requests that were served by fresh entries in cache");
252     mvprintw(9, 0, "Revalidate => Requests that contacted the origin to verify if still valid");
253     mvprintw(10, 0, "Cold       => Requests that were not in cache at all");
254     mvprintw(11, 0, "Changed    => Requests that required entries in cache to be updated");
255     mvprintw(12, 0, "Changed    => Requests that can't be cached for some reason");
256     mvprintw(12, 0, "No Cache   => Requests that the client sent Cache-Control: no-cache header");
257 
258     attron(COLOR_PAIR(colorPair::border));
259     attron(A_BOLD);
260     mvprintw(23, 0, "%s - %.12s - %.12s      (b)ack                            ", timeBuf, version.c_str(), host.c_str());
261     attroff(COLOR_PAIR(colorPair::border));
262     attroff(A_BOLD);
263     refresh();
264     int x = getch();
265     if (x == 'b') {
266       break;
267     }
268   }
269 }
270 
271 //----------------------------------------------------------------------------
272 void
main_stats_page(Stats & stats)273 main_stats_page(Stats &stats)
274 {
275   attron(COLOR_PAIR(colorPair::border));
276   attron(A_BOLD);
277   mvprintw(0, 0, "         CACHE INFORMATION             ");
278   mvprintw(0, 40, "       CLIENT REQUEST & RESPONSE        ");
279   mvprintw(16, 0, "             CLIENT                    ");
280   mvprintw(16, 40, "           ORIGIN SERVER                ");
281 
282   for (int i = 0; i <= 22; ++i) {
283     mvprintw(i, 39, " ");
284   }
285   attroff(COLOR_PAIR(colorPair::border));
286   attroff(A_BOLD);
287 
288   list<string> cache1;
289   cache1.push_back("disk_used");
290   cache1.push_back("disk_total");
291   cache1.push_back("ram_used");
292   cache1.push_back("ram_total");
293   cache1.push_back("lookups");
294   cache1.push_back("cache_writes");
295   cache1.push_back("cache_updates");
296   cache1.push_back("cache_deletes");
297   cache1.push_back("read_active");
298   cache1.push_back("write_active");
299   cache1.push_back("update_active");
300   cache1.push_back("entries");
301   cache1.push_back("avg_size");
302   cache1.push_back("dns_lookups");
303   cache1.push_back("dns_hits");
304   makeTable(0, 1, cache1, stats);
305 
306   list<string> cache2;
307   cache2.push_back("ram_ratio");
308   cache2.push_back("fresh");
309   cache2.push_back("reval");
310   cache2.push_back("cold");
311   cache2.push_back("changed");
312   cache2.push_back("not");
313   cache2.push_back("no");
314   cache2.push_back("fresh_time");
315   cache2.push_back("reval_time");
316   cache2.push_back("cold_time");
317   cache2.push_back("changed_time");
318   cache2.push_back("not_time");
319   cache2.push_back("no_time");
320   cache2.push_back("dns_ratio");
321   cache2.push_back("dns_entry");
322   makeTable(21, 1, cache2, stats);
323 
324   list<string> response1;
325   response1.push_back("get");
326   response1.push_back("head");
327   response1.push_back("post");
328   response1.push_back("2xx");
329   response1.push_back("3xx");
330   response1.push_back("4xx");
331   response1.push_back("5xx");
332   response1.push_back("conn_fail");
333   response1.push_back("other_err");
334   response1.push_back("abort");
335   makeTable(41, 1, response1, stats);
336 
337   list<string> response2;
338   response2.push_back("200");
339   response2.push_back("206");
340   response2.push_back("301");
341   response2.push_back("302");
342   response2.push_back("304");
343   response2.push_back("404");
344   response2.push_back("502");
345   response2.push_back("s_100");
346   response2.push_back("s_1k");
347   response2.push_back("s_3k");
348   response2.push_back("s_5k");
349   response2.push_back("s_10k");
350   response2.push_back("s_1m");
351   response2.push_back("s_>1m");
352   makeTable(62, 1, response2, stats);
353 
354   list<string> client1;
355   client1.push_back("client_req");
356   client1.push_back("client_req_conn");
357   client1.push_back("client_conn");
358   client1.push_back("client_curr_conn");
359   client1.push_back("client_actv_conn");
360   client1.push_back("client_dyn_ka");
361   makeTable(0, 17, client1, stats);
362 
363   list<string> client2;
364   client2.push_back("client_head");
365   client2.push_back("client_body");
366   client2.push_back("client_avg_size");
367   client2.push_back("client_net");
368   client2.push_back("client_req_time");
369   makeTable(21, 17, client2, stats);
370 
371   list<string> server1;
372   server1.push_back("server_req");
373   server1.push_back("server_req_conn");
374   server1.push_back("server_conn");
375   server1.push_back("server_curr_conn");
376   makeTable(41, 17, server1, stats);
377 
378   list<string> server2;
379   server2.push_back("server_head");
380   server2.push_back("server_body");
381   server2.push_back("server_avg_size");
382   server2.push_back("server_net");
383   makeTable(62, 17, server2, stats);
384 }
385 
386 //----------------------------------------------------------------------------
387 int
main(int argc,const char ** argv)388 main(int argc, const char **argv)
389 {
390 #if HAS_CURL
391   static const char USAGE[] = "Usage: traffic_top [-s seconds] [URL|hostname|hostname:port]";
392 #else
393   static const char USAGE[] = "Usage: traffic_top [-s seconds]";
394 #endif
395 
396   int sleep_time = 6; // In seconds
397   bool absolute  = false;
398   string url;
399 
400   AppVersionInfo version;
401   version.setup(PACKAGE_NAME, "traffic_top", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
402 
403   const ArgumentDescription argument_descriptions[] = {
404     {"sleep", 's', "Sets the delay between updates (in seconds)", "I", &sleep_time, nullptr, nullptr},
405     HELP_ARGUMENT_DESCRIPTION(),
406     VERSION_ARGUMENT_DESCRIPTION(),
407     RUNROOT_ARGUMENT_DESCRIPTION(),
408   };
409 
410   process_args(&version, argument_descriptions, countof(argument_descriptions), argv, USAGE);
411 
412   runroot_handler(argv);
413   Layout::create();
414   RecProcessInit(RECM_STAND_ALONE, nullptr /* diags */);
415   LibRecordsConfigInit();
416 
417   switch (n_file_arguments) {
418   case 0: {
419     ats_scoped_str rundir(RecConfigReadRuntimeDir());
420 
421     TSMgmtError err = TSInit(rundir, static_cast<TSInitOptionT>(TS_MGMT_OPT_NO_EVENTS | TS_MGMT_OPT_NO_SOCK_TESTS));
422     if (err != TS_ERR_OKAY) {
423       fprintf(stderr, "Error: connecting to local manager: %s\n", TSGetErrorMessage(err));
424       exit(1);
425     }
426     break;
427   }
428 
429   case 1:
430 #if HAS_CURL
431     url = file_arguments[0];
432 #else
433     usage(argument_descriptions, countof(argument_descriptions), USAGE);
434 #endif
435     break;
436 
437   default:
438     usage(argument_descriptions, countof(argument_descriptions), USAGE);
439   }
440 
441   Stats stats(url);
442   stats.getStats();
443   const string &host = stats.getHost();
444 
445   initscr();
446   curs_set(0);
447 
448   start_color(); /* Start color functionality	*/
449 
450   init_pair(colorPair::red, COLOR_RED, COLOR_BLACK);
451   init_pair(colorPair::yellow, COLOR_YELLOW, COLOR_BLACK);
452   init_pair(colorPair::grey, COLOR_BLACK, COLOR_BLACK);
453   init_pair(colorPair::green, COLOR_GREEN, COLOR_BLACK);
454   init_pair(colorPair::blue, COLOR_BLUE, COLOR_BLACK);
455   init_pair(colorPair::cyan, COLOR_CYAN, COLOR_BLACK);
456   init_pair(colorPair::border, COLOR_WHITE, COLOR_BLUE);
457   //  mvchgat(0, 0, -1, A_BLINK, 1, nullptr);
458 
459   enum Page {
460     MAIN_PAGE,
461     RESPONSE_PAGE,
462   };
463   Page page       = MAIN_PAGE;
464   string page_alt = "(r)esponse";
465 
466   while (true) {
467     attron(COLOR_PAIR(colorPair::border));
468     attron(A_BOLD);
469 
470     string version;
471     time_t now = time(nullptr);
472     struct tm nowtm;
473     char timeBuf[32];
474     localtime_r(&now, &nowtm);
475     strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", &nowtm);
476     stats.getStat("version", version);
477 
478     mvprintw(23, 0, "%-20.20s   %30s (q)uit (h)elp (%c)bsolute  ", host.c_str(), page_alt.c_str(), absolute ? 'A' : 'a');
479     attroff(COLOR_PAIR(colorPair::border));
480     attroff(A_BOLD);
481 
482     if (page == MAIN_PAGE) {
483       main_stats_page(stats);
484     } else if (page == RESPONSE_PAGE) {
485       response_code_page(stats);
486     }
487 
488     curs_set(0);
489     refresh();
490     timeout(sleep_time * 1000);
491 
492     int x = getch();
493     switch (x) {
494     case 'h':
495       help(host, version);
496       break;
497     case 'q':
498       goto quit;
499     case 'm':
500       page     = MAIN_PAGE;
501       page_alt = "(r)esponse";
502       break;
503     case 'r':
504       page     = RESPONSE_PAGE;
505       page_alt = "(m)ain";
506       break;
507     case 'a':
508       absolute = stats.toggleAbsolute();
509     }
510     stats.getStats();
511     clear();
512   }
513 
514 quit:
515   endwin();
516 
517   return 0;
518 }
519