ctorrent stat collector
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

365 lines
11KB

  1. /***************************************************************************
  2. * Copyright (C) 07/2007 by Olaf Rempel *
  3. * razzor@kopf-tisch.de *
  4. * *
  5. * This program is free software; you can redistribute it and/or modify *
  6. * it under the terms of the GNU General Public License as published by *
  7. * the Free Software Foundation; version 2 of the License *
  8. * *
  9. * This program is distributed in the hope that it will be useful, *
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  12. * GNU General Public License for more details. *
  13. * *
  14. * You should have received a copy of the GNU General Public License *
  15. * along with this program; if not, write to the *
  16. * Free Software Foundation, Inc., *
  17. * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
  18. ***************************************************************************/
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include <unistd.h>
  22. #include <string.h>
  23. #include <time.h>
  24. #include <sys/types.h>
  25. #include <sys/stat.h>
  26. #include "configfile.h"
  27. #include "event.h"
  28. #include "httpd.h"
  29. #include "linebuffer.h"
  30. #include "list.h"
  31. #include "logging.h"
  32. #include "sockaddr.h"
  33. #include "tcpsocket.h"
  34. #include "torrentfile.h"
  35. struct client_stats {
  36. /* current bandwidth up/down */
  37. int bw_up;
  38. int bw_dn;
  39. /* total bytes up/down */
  40. unsigned long long total_up;
  41. unsigned long long total_dn;
  42. /* clients cloud view */
  43. int chunk_total;
  44. int chunk_avail;
  45. int chunk_have;
  46. };
  47. struct client_con {
  48. struct list_head list;
  49. struct torrent_file *torrent;
  50. struct sockaddr_in addr;
  51. struct event_fd *event;
  52. struct linebuffer *lbuf;
  53. /* download stats */
  54. struct client_stats stats;
  55. /* timestamp when this client completed */
  56. long completed;
  57. };
  58. static struct event_fd *listen_event;
  59. static struct event_fd *httpd_event;
  60. static struct event_timeout *timeout_event;
  61. static void free_client(struct client_con *con)
  62. {
  63. list_del(&con->list);
  64. /* remove torrents without clients */
  65. if (list_empty(&con->torrent->client_list))
  66. destroy_torrent(con->torrent);
  67. close(event_get_fd(con->event));
  68. event_remove_fd(con->event);
  69. free(con->lbuf);
  70. free(con);
  71. }
  72. static int ctcs_data_handler(int fd, void *privdata)
  73. {
  74. struct client_con *con = (struct client_con *)privdata;
  75. struct client_stats *stats = &con->stats;
  76. /* get data from socket */
  77. if (linebuffer_readfd(con->lbuf, fd) < 0) {
  78. free_client(con);
  79. return -1;
  80. }
  81. char *line;
  82. while ((line = linebuffer_getline(con->lbuf, NULL)) != NULL) {
  83. /* bandwidth update */
  84. if (strncmp(line, "CTBW ", 5) == 0) {
  85. int bwup, bwdn, liup, lidn;
  86. if (sscanf(line +5, "%d,%d %d,%d", &bwdn, &bwup, &lidn, &liup) == 4) {
  87. stats->bw_up = bwup;
  88. stats->bw_dn = bwdn;
  89. }
  90. /* status update (assume protocol v2) */
  91. } else if (strncmp(line, "CTSTATUS ", 9) == 0) {
  92. int seeds1 = 0, seeds2 = 0, leech1 = 0, leech2 = 0, count = 0;
  93. int chunk1 = 0, chunk2 = 0, chunk3 = 0, bwdn = 0, bwup = 0;
  94. int lidn = 0, liup = 0, cache = 0;
  95. unsigned long long totdn = 0, totup = 0;
  96. if (sscanf(line +9, "%d:%d/%d:%d/%d %d/%d/%d %d,%d %llu,%llu %d,%d %d",
  97. &seeds1, &seeds2, &leech1, &leech2, &count,
  98. &chunk1, &chunk2, &chunk3,
  99. &bwdn, &bwup, &totdn, &totup,
  100. &lidn, &liup, &cache) == 15) {
  101. stats->bw_up = bwup;
  102. stats->bw_dn = bwdn;
  103. stats->total_up = totup;
  104. stats->total_dn = totdn;
  105. stats->chunk_have = chunk1;
  106. stats->chunk_total = chunk2;
  107. stats->chunk_avail = chunk3;
  108. }
  109. /* update torrent-file */
  110. } else if (strncmp(line, "CTORRENT ", 9) == 0) {
  111. char *filename = strrchr(line +9, ' ');
  112. if (filename != NULL && strcmp(filename, con->torrent->name) != 0) {
  113. struct torrent_file *torrent = find_create_torrent(filename +1);
  114. if (torrent != NULL) {
  115. list_del(&con->list);
  116. list_add_tail(&con->list, &torrent->client_list);
  117. con->torrent = torrent;
  118. }
  119. }
  120. }
  121. linebuffer_freeline(con->lbuf);
  122. }
  123. /* client completed? */
  124. if (stats->chunk_have == stats->chunk_total && stats->chunk_total != 0 && con->completed == 0) {
  125. con->completed = time(NULL);
  126. list_del(&con->list);
  127. /* sorted insert, completed on top of list, ordered by complete-timestamp */
  128. struct client_con *search;
  129. list_for_each_entry(search, &con->torrent->client_list, list) {
  130. if (search->completed == 0)
  131. break;
  132. if (search->completed > con->completed)
  133. break;
  134. }
  135. list_add_tail(&con->list, &search->list);
  136. return 0;
  137. }
  138. return 0;
  139. }
  140. static int ctcs_trigger_status(void *privdata)
  141. {
  142. long timeout = time(NULL) - (int)privdata;
  143. struct torrent_file *torrent;
  144. list_for_each_entry(torrent, &torrent_list, list) {
  145. int seeder = 0;
  146. int leecher = 0;
  147. struct client_con *con;
  148. list_for_each_entry(con, &torrent->client_list, list) {
  149. write(event_get_fd(con->event), "SENDSTATUS\n", 11);
  150. if (con->completed)
  151. seeder++;
  152. else
  153. leecher++;
  154. }
  155. /* no seeders available and no local seeder running => spawn one */
  156. if (leecher > 0 && seeder == 0 && torrent->child == NULL)
  157. seed_torrent(torrent);
  158. /* delete holds the number of clients to quit */
  159. int delete = seeder - leecher;
  160. list_for_each_entry(con, &torrent->client_list, list) {
  161. if (delete <= 0)
  162. break;
  163. if (con->completed == 0 || con->completed > timeout)
  164. continue;
  165. write(event_get_fd(con->event), "CTQUIT\n", 7);
  166. delete--;
  167. }
  168. }
  169. return 0;
  170. }
  171. static int ctcs_accept_handler(int fd, void *privdata)
  172. {
  173. struct client_con *con = malloc(sizeof(struct client_con));
  174. if (con == NULL) {
  175. log_print(LOG_WARN, "accept_cb(): out of memory");
  176. return 0;
  177. }
  178. memset(con, 0, sizeof(struct client_con));
  179. con->lbuf = create_linebuffer(1024);
  180. if (con->lbuf == NULL) {
  181. log_print(LOG_WARN, "accept_cb(): out of memory");
  182. free(con);
  183. return 0;
  184. }
  185. int sockfd = tcp_accept(fd, &con->addr);
  186. if (sockfd < 0) {
  187. log_print(LOG_WARN, "accept_cb(): tcp_accept()");
  188. free(con->lbuf);
  189. free(con);
  190. return 0;
  191. }
  192. con->event = event_add_readfd(NULL, sockfd, ctcs_data_handler, con);
  193. /* assign default torrent */
  194. con->torrent = find_create_torrent("[unknown]");
  195. list_add_tail(&con->list, &con->torrent->client_list);
  196. return 0;
  197. }
  198. static int ctcs_httpd_show(struct httpd_con *con, void *privdata)
  199. {
  200. struct linebuffer *lbuf = create_linebuffer(16384);
  201. if (lbuf == NULL) {
  202. httpd_send_error(con, "500 ERROR", "Out of Memory");
  203. return -1;
  204. }
  205. linebuffer_printf(lbuf, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n");
  206. linebuffer_printf(lbuf, "<html><head><meta http-equiv=\"refresh\" content=\"30;\"></head><body><h1>ctorrent stats</h1>\n");
  207. struct torrent_file *torrent;
  208. list_for_each_entry(torrent, &torrent_list, list) {
  209. if (list_empty(&torrent->client_list))
  210. continue;
  211. linebuffer_printf(lbuf, "<table border=\"1\">\n<tr><td colspan=\"6\" align=\"center\">%s</td></tr>\n", torrent->name);
  212. linebuffer_printf(lbuf, "<tr></td><td><b>Client IP:Port</b></td><td><b>Chunks (have/total/avail)</b></td><td><b>Download total(current)</b></td>");
  213. linebuffer_printf(lbuf, "<td><b>Upload total(current)</b></td><td><b>Completed since</b></td><td><b>Quit</b></td></tr>\n");
  214. struct client_con *tmp;
  215. list_for_each_entry(tmp, &torrent->client_list, list) {
  216. struct client_stats *stats = &tmp->stats;
  217. linebuffer_printf(lbuf, "<tr><td align=\"right\">%s</td><td align=\"right\">%3.2lf%% (%d/%d/%d)</td>",
  218. get_sockaddr_buf(&tmp->addr),
  219. (double)stats->chunk_have / (double)stats->chunk_total * 100.0,
  220. stats->chunk_have,
  221. stats->chunk_total,
  222. stats->chunk_avail);
  223. linebuffer_printf(lbuf, "<td align=\"right\">%llu (%d)</td><td align=\"right\">%llu (%d)</td><td align=\"right\">%s</td>",
  224. stats->total_dn,
  225. stats->bw_dn,
  226. stats->total_up,
  227. stats->bw_up,
  228. (tmp->completed != 0) ? ctime(&tmp->completed) : "-");
  229. linebuffer_printf(lbuf, "<td><a href=\"/quit?client=%s\">Quit</td></tr>\n", get_sockaddr_buf(&tmp->addr));
  230. }
  231. linebuffer_printf(lbuf, "</table>\n<br><br>\n");
  232. }
  233. linebuffer_printf(lbuf, "</body></html>\n");
  234. linebuffer_writefd(lbuf, con->fd);
  235. linebuffer_free(lbuf);
  236. return 0;
  237. }
  238. static int ctcs_httpd_quit(struct httpd_con *con, void *privdata)
  239. {
  240. if (con->req_arg_cnt == 2 && strncmp(con->req_args[1], "client=", 7) == 0) {
  241. struct sockaddr_in addr;
  242. if (parse_sockaddr(con->req_args[1] +7, &addr) == 0) {
  243. struct torrent_file *torrent;
  244. list_for_each_entry(torrent, &torrent_list, list) {
  245. struct client_con *search;
  246. list_for_each_entry(search, &torrent->client_list, list) {
  247. if (!same_sockaddr(&search->addr, &addr))
  248. continue;
  249. write(event_get_fd(search->event), "CTQUIT\n", 7);
  250. }
  251. }
  252. }
  253. }
  254. char *text = "HTTP/1.0 302 OK\r\nContent-Type: text/html\r\nConnection: close\r\nLocation: /\r\n\r\n";
  255. write(con->fd, text, strlen(text));
  256. return 0;
  257. }
  258. int ctcs_init(void)
  259. {
  260. const char *listen_addr = config_get_string("global", "listen", "0.0.0.0:2780");
  261. listen_event = tcp_listen_event(listen_addr, ctcs_accept_handler, NULL);
  262. if (listen_event == NULL)
  263. return -1;
  264. const char *httpd_addr = config_get_string("global", "listen-http", "0.0.0.0:8080");
  265. httpd_event = tcp_listen_event(httpd_addr, httpd_accept_handler, NULL);
  266. if (httpd_event == NULL) {
  267. event_remove_fd(listen_event);
  268. close(event_get_fd(listen_event));
  269. return -1;
  270. }
  271. httpd_add_cb("/quit", 0, ctcs_httpd_quit, NULL);
  272. httpd_add_cb("/", 1, ctcs_httpd_show, NULL);
  273. int interval = config_get_int("global", "status-interval", 10);
  274. int timeout = config_get_int("global", "seed-timeout", 60);
  275. struct timeval tv = { .tv_sec = interval, .tv_usec = 0 };
  276. timeout_event = event_add_timeout(&tv, ctcs_trigger_status, (void *)timeout);
  277. if (timeout_event == NULL) {
  278. event_remove_fd(httpd_event);
  279. close(event_get_fd(httpd_event));
  280. event_remove_fd(listen_event);
  281. close(event_get_fd(listen_event));
  282. return -1;
  283. }
  284. return 0;
  285. }
  286. int ctcs_free(void)
  287. {
  288. struct torrent_file *torrent, *torrent2;
  289. list_for_each_entry_safe(torrent, torrent2, &torrent_list, list) {
  290. struct client_con *con, *con2;
  291. list_for_each_entry_safe(con, con2, &torrent->client_list, list) {
  292. free_client(con);
  293. }
  294. }
  295. event_remove_timeout(timeout_event);
  296. event_remove_fd(httpd_event);
  297. close(event_get_fd(httpd_event));
  298. httpd_free();
  299. event_remove_fd(listen_event);
  300. close(event_get_fd(listen_event));
  301. return 0;
  302. }