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.

285 lines
7.5KB

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <string.h>
  5. #include <time.h>
  6. #include "event.h"
  7. #include "httpd.h"
  8. #include "linebuffer.h"
  9. #include "list.h"
  10. #include "logging.h"
  11. #include "sockaddr.h"
  12. #include "tcpsocket.h"
  13. static LIST_HEAD(torrent_list);
  14. struct torrent_file {
  15. struct list_head list;
  16. struct list_head client_list;
  17. char *name;
  18. };
  19. struct client_con {
  20. struct list_head list;
  21. struct sockaddr_in addr;
  22. struct event_fd *event;
  23. struct linebuffer *lbuf;
  24. int bw_up;
  25. int bw_dn;
  26. unsigned long long total_up;
  27. unsigned long long total_dn;
  28. int chunk_total;
  29. int chunk_avail;
  30. int chunk_have;
  31. long completed;
  32. struct torrent_file *torrent;
  33. };
  34. static struct torrent_file * find_create_torrent(const char *filename)
  35. {
  36. struct torrent_file *torrent;
  37. list_for_each_entry(torrent, &torrent_list, list) {
  38. if (strcmp(torrent->name, filename) == 0)
  39. return torrent;
  40. }
  41. torrent = malloc(sizeof(struct torrent_file) + strlen(filename));
  42. if (torrent == NULL) {
  43. log_print(LOG_WARN, "find_create_torrent(): out of memory");
  44. return NULL;
  45. }
  46. INIT_LIST_HEAD(&torrent->client_list);
  47. torrent->name = strdup(filename);
  48. struct torrent_file *search;
  49. list_for_each_entry(search, &torrent_list, list)
  50. if (strcmp(search->name, torrent->name) > 0)
  51. break;
  52. list_add_tail(&torrent->list, &search->list);
  53. return torrent;
  54. }
  55. static void free_client(struct client_con *con)
  56. {
  57. list_del(&con->list);
  58. /* remove torrents without clients */
  59. if (list_empty(&con->torrent->client_list)) {
  60. list_del(&con->torrent->list);
  61. free(con->torrent->name);
  62. free(con->torrent);
  63. }
  64. close(event_get_fd(con->event));
  65. event_remove_fd(con->event);
  66. free(con->lbuf);
  67. free(con);
  68. }
  69. static int data_cb(int fd, void *privdata)
  70. {
  71. struct client_con *con = (struct client_con *)privdata;
  72. if (linebuffer_readfd(con->lbuf, fd) < 0) {
  73. free_client(con);
  74. return -1;
  75. }
  76. char *line;
  77. while ((line = linebuffer_getline(con->lbuf, NULL)) != NULL) {
  78. if (strncmp(line, "CTBW ", 5) == 0) {
  79. int bwup, bwdn, liup, lidn;
  80. if (sscanf(line +5, "%d,%d %d,%d", &bwdn, &bwup, &lidn, &liup) == 4) {
  81. con->bw_up = bwup;
  82. con->bw_dn = bwdn;
  83. }
  84. } else if (strncmp(line, "CTSTATUS ", 9) == 0) {
  85. int seeds1 = 0, seeds2 = 0, leech1 = 0, leech2 = 0, count = 0;
  86. int chunk1 = 0, chunk2 = 0, chunk3 = 0, bwdn = 0, bwup = 0;
  87. int lidn = 0, liup = 0, cache = 0;
  88. unsigned long long totdn = 0, totup = 0;
  89. if (sscanf(line +9, "%d:%d/%d:%d/%d %d/%d/%d %d,%d %llu,%llu %d,%d %d",
  90. &seeds1, &seeds2, &leech1, &leech2, &count,
  91. &chunk1, &chunk2, &chunk3,
  92. &bwdn, &bwup, &totdn, &totup,
  93. &lidn, &liup, &cache) == 15) {
  94. con->total_up = totup;
  95. con->total_dn = totdn;
  96. con->chunk_have = chunk1;
  97. con->chunk_total = chunk2;
  98. con->chunk_avail = chunk3;
  99. }
  100. } else if (strncmp(line, "CTORRENT ", 9) == 0) {
  101. char *filename = strrchr(line +9, ' ');
  102. if (filename != NULL) {
  103. struct torrent_file *torrent = find_create_torrent(filename +1);
  104. if (torrent != NULL) {
  105. list_del(&con->list);
  106. con->torrent = torrent;
  107. list_add_tail(&con->list, &con->torrent->client_list);
  108. }
  109. }
  110. }
  111. linebuffer_freeline(con->lbuf);
  112. }
  113. /* move completed clients to top of the list, ordered by their timestamp */
  114. if (con->chunk_have == con->chunk_total && con->chunk_total != 0 && con->completed == 0) {
  115. con->completed = time(NULL);
  116. list_del(&con->list);
  117. struct client_con *search;
  118. list_for_each_entry(search, &con->torrent->client_list, list) {
  119. if (search->completed == 0)
  120. break;
  121. if (search->completed > con->completed)
  122. break;
  123. }
  124. list_add_tail(&con->list, &search->list);
  125. return 0;
  126. }
  127. return 0;
  128. }
  129. int ctcs_trigger_status(void *privdata)
  130. {
  131. long timeout = time(NULL) - 300;
  132. struct torrent_file *torrent;
  133. list_for_each_entry(torrent, &torrent_list, list) {
  134. int delete = 0;
  135. struct client_con *con;
  136. list_for_each_entry(con, &torrent->client_list, list) {
  137. write(event_get_fd(con->event), "SENDSTATUS\n", 11);
  138. delete += (con->completed == 0) ? -1 : 1;
  139. }
  140. /* delete holds the number of clients to quit */
  141. list_for_each_entry(con, &torrent->client_list, list) {
  142. if (delete <= 0)
  143. break;
  144. if (con->completed == 0 || con->completed > timeout)
  145. continue;
  146. write(event_get_fd(con->event), "CTQUIT\n", 7);
  147. delete--;
  148. }
  149. }
  150. return 0;
  151. }
  152. int ctcs_accept_handler(int fd, void *privdata)
  153. {
  154. struct client_con *con = malloc(sizeof(struct client_con));
  155. if (con == NULL) {
  156. log_print(LOG_WARN, "accept_cb(): out of memory");
  157. return 0;
  158. }
  159. memset(con, 0, sizeof(struct client_con));
  160. con->lbuf = create_linebuffer(1024);
  161. if (con->lbuf == NULL) {
  162. log_print(LOG_WARN, "accept_cb(): out of memory");
  163. free(con);
  164. return 0;
  165. }
  166. unsigned int i = sizeof(con->addr);
  167. int sockfd = accept(fd, (struct sockaddr *)&con->addr, &i);
  168. if (sockfd < 0) {
  169. log_print(LOG_WARN, "accept_cb(): accept()");
  170. free(con->lbuf);
  171. free(con);
  172. return 0;
  173. }
  174. con->event = event_add_readfd(NULL, sockfd, data_cb, con);
  175. con->torrent = find_create_torrent("[unknown]");
  176. list_add_tail(&con->list, &con->torrent->client_list);
  177. return 0;
  178. }
  179. int ctcs_httpd_show(struct httpd_con *con, void *privdata)
  180. {
  181. struct linebuffer *lbuf = create_linebuffer(16384);
  182. if (lbuf == NULL) {
  183. httpd_send_error(con, "500 ERROR", "Out of Memory");
  184. return -1;
  185. }
  186. linebuffer_printf(lbuf, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n");
  187. linebuffer_printf(lbuf, "<html><head><meta http-equiv=\"refresh\" content=\"30;\"></head><body><h1>ctorrent stats</h1>\n");
  188. struct torrent_file *torrent;
  189. list_for_each_entry(torrent, &torrent_list, list) {
  190. if (list_empty(&torrent->client_list))
  191. continue;
  192. linebuffer_printf(lbuf, "<table border=\"1\">\n<tr><td colspan=\"6\" align=\"center\">%s</td></tr>\n", torrent->name);
  193. 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>");
  194. linebuffer_printf(lbuf, "<td><b>Upload total(current)</b></td><td><b>Completed since</b></td><td><b>Quit</b></td></tr>\n");
  195. struct client_con *tmp;
  196. list_for_each_entry(tmp, &torrent->client_list, list) {
  197. linebuffer_printf(lbuf, "<tr><td align=\"right\">%s</td><td align=\"right\">%3.2lf%% (%d/%d/%d)</td>",
  198. get_sockaddr_buf(&tmp->addr),
  199. (double)tmp->chunk_have / (double)tmp->chunk_total * 100.0, tmp->chunk_have, tmp->chunk_total, tmp->chunk_avail);
  200. linebuffer_printf(lbuf, "<td align=\"right\">%llu (%d)</td><td align=\"right\">%llu (%d)</td><td align=\"right\">%s</td>",
  201. tmp->total_dn, tmp->bw_dn, tmp->total_up, tmp->bw_up,
  202. (tmp->completed != 0) ? ctime(&tmp->completed) : "-");
  203. linebuffer_printf(lbuf, "<td><a href=\"/quit?client=%s\">Quit</td></tr>\n", get_sockaddr_buf(&tmp->addr));
  204. }
  205. linebuffer_printf(lbuf, "</table>\n<br><br>\n");
  206. }
  207. linebuffer_printf(lbuf, "</body></html>\n");
  208. linebuffer_writefd(lbuf, con->fd);
  209. linebuffer_free(lbuf);
  210. return 0;
  211. }
  212. int ctcs_httpd_quit(struct httpd_con *con, void *privdata)
  213. {
  214. if (con->req_arg_cnt == 2 && strncmp(con->req_args[1], "client=", 7) == 0) {
  215. struct sockaddr_in addr;
  216. if (parse_sockaddr(con->req_args[1] +7, &addr) == 0) {
  217. struct torrent_file *torrent;
  218. list_for_each_entry(torrent, &torrent_list, list) {
  219. struct client_con *search;
  220. list_for_each_entry(search, &torrent->client_list, list) {
  221. if (!same_sockaddr(&search->addr, &addr))
  222. continue;
  223. write(event_get_fd(search->event), "CTQUIT\n", 7);
  224. }
  225. }
  226. }
  227. }
  228. char *text = "HTTP/1.0 302 OK\r\nContent-Type: text/html\r\nConnection: close\r\nLocation: /\r\n\r\n";
  229. write(con->fd, text, strlen(text));
  230. return 0;
  231. }