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
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
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
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
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
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 = localtime(&now);
233     char timeBuf[32];
234     strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", nowtm);
235 
236     // clear();
237     attron(A_BOLD);
238     mvprintw(0, 0, "Overview:");
239     attroff(A_BOLD);
240     mvprintw(
241       1, 0,
242       "traffic_top is a top like program for Apache Traffic Server (ATS). "
243       "There is a lot of statistical information gathered by ATS. "
244       "This program tries to show some of the more important stats and gives a good overview of what the proxy server is doing. "
245       "Hopefully this can be used as a tool for diagnosing the proxy server if there are problems.");
246 
247     attron(A_BOLD);
248     mvprintw(7, 0, "Definitions:");
249     attroff(A_BOLD);
250     mvprintw(8, 0, "Fresh      => Requests that were served by fresh entries in cache");
251     mvprintw(9, 0, "Revalidate => Requests that contacted the origin to verify if still valid");
252     mvprintw(10, 0, "Cold       => Requests that were not in cache at all");
253     mvprintw(11, 0, "Changed    => Requests that required entries in cache to be updated");
254     mvprintw(12, 0, "Changed    => Requests that can't be cached for some reason");
255     mvprintw(12, 0, "No Cache   => Requests that the client sent Cache-Control: no-cache header");
256 
257     attron(COLOR_PAIR(colorPair::border));
258     attron(A_BOLD);
259     mvprintw(23, 0, "%s - %.12s - %.12s      (b)ack                            ", timeBuf, version.c_str(), host.c_str());
260     attroff(COLOR_PAIR(colorPair::border));
261     attroff(A_BOLD);
262     refresh();
263     int x = getch();
264     if (x == 'b') {
265       break;
266     }
267   }
268 }
269 
270 //----------------------------------------------------------------------------
271 void
272 main_stats_page(Stats &stats)
273 {
274   attron(COLOR_PAIR(colorPair::border));
275   attron(A_BOLD);
276   mvprintw(0, 0, "         CACHE INFORMATION             ");
277   mvprintw(0, 40, "       CLIENT REQUEST & RESPONSE        ");
278   mvprintw(16, 0, "             CLIENT                    ");
279   mvprintw(16, 40, "           ORIGIN SERVER                ");
280 
281   for (int i = 0; i <= 22; ++i) {
282     mvprintw(i, 39, " ");
283   }
284   attroff(COLOR_PAIR(colorPair::border));
285   attroff(A_BOLD);
286 
287   list<string> cache1;
288   cache1.push_back("disk_used");
289   cache1.push_back("disk_total");
290   cache1.push_back("ram_used");
291   cache1.push_back("ram_total");
292   cache1.push_back("lookups");
293   cache1.push_back("cache_writes");
294   cache1.push_back("cache_updates");
295   cache1.push_back("cache_deletes");
296   cache1.push_back("read_active");
297   cache1.push_back("write_active");
298   cache1.push_back("update_active");
299   cache1.push_back("entries");
300   cache1.push_back("avg_size");
301   cache1.push_back("dns_lookups");
302   cache1.push_back("dns_hits");
303   makeTable(0, 1, cache1, stats);
304 
305   list<string> cache2;
306   cache2.push_back("ram_ratio");
307   cache2.push_back("fresh");
308   cache2.push_back("reval");
309   cache2.push_back("cold");
310   cache2.push_back("changed");
311   cache2.push_back("not");
312   cache2.push_back("no");
313   cache2.push_back("fresh_time");
314   cache2.push_back("reval_time");
315   cache2.push_back("cold_time");
316   cache2.push_back("changed_time");
317   cache2.push_back("not_time");
318   cache2.push_back("no_time");
319   cache2.push_back("dns_ratio");
320   cache2.push_back("dns_entry");
321   makeTable(21, 1, cache2, stats);
322 
323   list<string> response1;
324   response1.push_back("get");
325   response1.push_back("head");
326   response1.push_back("post");
327   response1.push_back("2xx");
328   response1.push_back("3xx");
329   response1.push_back("4xx");
330   response1.push_back("5xx");
331   response1.push_back("conn_fail");
332   response1.push_back("other_err");
333   response1.push_back("abort");
334   makeTable(41, 1, response1, stats);
335 
336   list<string> response2;
337   response2.push_back("200");
338   response2.push_back("206");
339   response2.push_back("301");
340   response2.push_back("302");
341   response2.push_back("304");
342   response2.push_back("404");
343   response2.push_back("502");
344   response2.push_back("s_100");
345   response2.push_back("s_1k");
346   response2.push_back("s_3k");
347   response2.push_back("s_5k");
348   response2.push_back("s_10k");
349   response2.push_back("s_1m");
350   response2.push_back("s_>1m");
351   makeTable(62, 1, response2, stats);
352 
353   list<string> client1;
354   client1.push_back("client_req");
355   client1.push_back("client_req_conn");
356   client1.push_back("client_conn");
357   client1.push_back("client_curr_conn");
358   client1.push_back("client_actv_conn");
359   client1.push_back("client_dyn_ka");
360   makeTable(0, 17, client1, stats);
361 
362   list<string> client2;
363   client2.push_back("client_head");
364   client2.push_back("client_body");
365   client2.push_back("client_avg_size");
366   client2.push_back("client_net");
367   client2.push_back("client_req_time");
368   makeTable(21, 17, client2, stats);
369 
370   list<string> server1;
371   server1.push_back("server_req");
372   server1.push_back("server_req_conn");
373   server1.push_back("server_conn");
374   server1.push_back("server_curr_conn");
375   makeTable(41, 17, server1, stats);
376 
377   list<string> server2;
378   server2.push_back("server_head");
379   server2.push_back("server_body");
380   server2.push_back("server_avg_size");
381   server2.push_back("server_net");
382   makeTable(62, 17, server2, stats);
383 }
384 
385 //----------------------------------------------------------------------------
386 int
387 main(int argc, const char **argv)
388 {
389 #if HAS_CURL
390   static const char USAGE[] = "Usage: traffic_top [-s seconds] [URL|hostname|hostname:port]";
391 #else
392   static const char USAGE[] = "Usage: traffic_top [-s seconds]";
393 #endif
394 
395   int sleep_time = 6; // In seconds
396   bool absolute  = false;
397   string url;
398 
399   AppVersionInfo version;
400   version.setup(PACKAGE_NAME, "traffic_top", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
401 
402   const ArgumentDescription argument_descriptions[] = {
403     {"sleep", 's', "Enable debugging output", "I", &sleep_time, nullptr, nullptr},
404     HELP_ARGUMENT_DESCRIPTION(),
405     VERSION_ARGUMENT_DESCRIPTION(),
406     RUNROOT_ARGUMENT_DESCRIPTION(),
407   };
408 
409   process_args(&version, argument_descriptions, countof(argument_descriptions), argv, USAGE);
410 
411   runroot_handler(argv);
412   Layout::create();
413   RecProcessInit(RECM_STAND_ALONE, nullptr /* diags */);
414   LibRecordsConfigInit();
415 
416   switch (n_file_arguments) {
417   case 0: {
418     ats_scoped_str rundir(RecConfigReadRuntimeDir());
419 
420     TSMgmtError err = TSInit(rundir, static_cast<TSInitOptionT>(TS_MGMT_OPT_NO_EVENTS | TS_MGMT_OPT_NO_SOCK_TESTS));
421     if (err != TS_ERR_OKAY) {
422       fprintf(stderr, "Error: connecting to local manager: %s\n", TSGetErrorMessage(err));
423       exit(1);
424     }
425     break;
426   }
427 
428   case 1:
429 #if HAS_CURL
430     url = file_arguments[0];
431 #else
432     usage(argument_descriptions, countof(argument_descriptions), USAGE);
433 #endif
434     break;
435 
436   default:
437     usage(argument_descriptions, countof(argument_descriptions), USAGE);
438   }
439 
440   Stats stats(url);
441   stats.getStats();
442   const string &host = stats.getHost();
443 
444   initscr();
445   curs_set(0);
446 
447   start_color(); /* Start color functionality	*/
448 
449   init_pair(colorPair::red, COLOR_RED, COLOR_BLACK);
450   init_pair(colorPair::yellow, COLOR_YELLOW, COLOR_BLACK);
451   init_pair(colorPair::grey, COLOR_BLACK, COLOR_BLACK);
452   init_pair(colorPair::green, COLOR_GREEN, COLOR_BLACK);
453   init_pair(colorPair::blue, COLOR_BLUE, COLOR_BLACK);
454   init_pair(colorPair::cyan, COLOR_CYAN, COLOR_BLACK);
455   init_pair(colorPair::border, COLOR_WHITE, COLOR_BLUE);
456   //  mvchgat(0, 0, -1, A_BLINK, 1, nullptr);
457 
458   enum Page {
459     MAIN_PAGE,
460     RESPONSE_PAGE,
461   };
462   Page page       = MAIN_PAGE;
463   string page_alt = "(r)esponse";
464 
465   while (true) {
466     attron(COLOR_PAIR(colorPair::border));
467     attron(A_BOLD);
468 
469     string version;
470     time_t now       = time(nullptr);
471     struct tm *nowtm = localtime(&now);
472     char timeBuf[32];
473     strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", nowtm);
474     stats.getStat("version", version);
475 
476     mvprintw(23, 0, "%-20.20s   %30s (q)uit (h)elp (%c)bsolute  ", host.c_str(), page_alt.c_str(), absolute ? 'A' : 'a');
477     attroff(COLOR_PAIR(colorPair::border));
478     attroff(A_BOLD);
479 
480     if (page == MAIN_PAGE) {
481       main_stats_page(stats);
482     } else if (page == RESPONSE_PAGE) {
483       response_code_page(stats);
484     }
485 
486     curs_set(0);
487     refresh();
488     timeout(sleep_time * 1000);
489 
490     int x = getch();
491     switch (x) {
492     case 'h':
493       help(host, version);
494       break;
495     case 'q':
496       goto quit;
497     case 'm':
498       page     = MAIN_PAGE;
499       page_alt = "(r)esponse";
500       break;
501     case 'r':
502       page     = RESPONSE_PAGE;
503       page_alt = "(m)ain";
504       break;
505     case 'a':
506       absolute = stats.toggleAbsolute();
507     }
508     stats.getStats();
509     clear();
510   }
511 
512 quit:
513   endwin();
514 
515   return 0;
516 }
517