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