1 /*
2  *  statsgw.cpp
3  *  HTTP server for showing some DL stats via SwarmPlayer 3000's webUI,
4  *  libevent based
5  *
6  *  Created by Victor Grishchenko, Arno Bakker
7  *  Copyright 2010-2012 TECHNISCHE UNIVERSITEIT DELFT. All rights reserved.
8  *
9  */
10 
11 #include "swift.h"
12 #include <event2/http.h>
13 
14 using namespace swift;
15 
16 int statsgw_reqs_count = 0;
17 
18 
19 uint64_t statsgw_last_down;
20 uint64_t statsgw_last_up;
21 tint statsgw_last_time = 0;
22 bool statsgw_quit_process=false;
23 struct evhttp *statsgw_event;
24 struct evhttp_bound_socket *statsgw_handle;
25 
26 
27 const char *top_page = "<!doctype html> \
28 <html style=\"font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px;background-color:white;\"> \
29 <head> \
30 	<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> \
31 	<meta http-equiv=\"cache-control\" content=\"Private\" /> \
32 	<meta http-equiv=\"Refresh\" content=\"2;url=http://127.0.0.1:6876/webUI\" /> \
33 	<title>Swift Web Interface</title> \
34 </head> \
35 <body style=\"font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px;background-color:white;padding:20px; margin:20px;\"> \
36 <div style=\"padding:40;\"> \
37 <div style=\"width:400; float:left; padding:20px\"> \
38 <h1 style=\"font-size: 20px;\"> Swift swarms: </h1>";
39 
40 const char *swarm_page_templ = " \
41 <h2 style=\"font-size: 18px; padding-top: 10px; padding-left: 20px; margin-bottom: 0px; color:#30bf00;\">Root hash: %s</h2> \
42    <ul style=\"padding-left: 40px;\"> \
43     <li>Progress:       %d%c \
44     <li>Download speed: %d KB/s \
45     <li>Upload speed:   %d KB/s \
46 </ul>";
47 
48 
49 const char *bottom_page = " \
50 <button style=\"color:white; background-color:#4f84dc; width:80;height:50; font-size:18px; font-weight:bold; text-shadow: #6374AB 2px 2px 2px;\" \
51 onClick=\"window.location='http://127.0.0.1:6876/webUI/exit';\">Quit Swift</button> \
52 </div> \
53 </div> \
54 </body> \
55 </html>";
56 
57 
58 const char *exit_page = "<!doctype html> \
59 <html style=\"font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px;background-color:white;\"> \
60 <head> \
61 	<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> \
62 	<meta http-equiv=\"cache-control\" content=\"Private\" /> \
63 	<title>Swift Web Interface</title> \
64 </head> \
65 <body style=\"font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px;background-color:white;padding:20px; margin:20px;\"> \
66 <div style=\"padding:40;\"> \
67 <div style=\"width:400; float:left; padding:20px\"> \
68 <h1 style=\"font-size: 20px;\"> Swift is no longer running. </h1> \
69 </div> \
70 </div> \
71 </body> \
72 </html>";
73 
74 
75 static void StatsGwNewRequestCallback(struct evhttp_request *evreq, void *arg);
76 
77 
StatsExitCallback(struct evhttp_request * evreq)78 void StatsExitCallback(struct evhttp_request *evreq)
79 {
80     char contlenstr[1024];
81     sprintf(contlenstr,PRISIZET,strlen(exit_page));
82     struct evkeyvalq *headers = evhttp_request_get_output_headers(evreq);
83     evhttp_add_header(headers, "Connection", "close");
84     evhttp_add_header(headers, "Content-Type", "text/html");
85     evhttp_add_header(headers, "Content-Length", contlenstr);
86     evhttp_add_header(headers, "Accept-Ranges", "none");
87 
88     // Construct evbuffer and send via chunked encoding
89     struct evbuffer *evb = evbuffer_new();
90     int ret = evbuffer_add(evb,exit_page,strlen(exit_page));
91     if (ret < 0) {
92         print_error("statsgw: ExitCallback: error evbuffer_add");
93         return;
94     }
95 
96     evhttp_send_reply(evreq, 200, "OK", evb);
97     evbuffer_free(evb);
98 }
99 
100 
StatsQuit()101 bool StatsQuit()
102 {
103     return statsgw_quit_process;
104 }
105 
106 
StatsOverviewCallback(struct evhttp_request * evreq)107 void StatsOverviewCallback(struct evhttp_request *evreq)
108 {
109     tint nu = NOW;
110     uint64_t down = Channel::global_raw_bytes_down;
111     uint64_t up = Channel::global_raw_bytes_up;
112 
113     int dspeed = 0, uspeed = 0;
114     tint tdiff = (nu - statsgw_last_time)/1000000;
115     if (tdiff > 0) {
116         dspeed = (int)(((down-statsgw_last_down)/1024) / tdiff);
117         uspeed = (int)(((up-statsgw_last_up)/1024) / tdiff);
118     }
119     //statsgw_last_down = down;
120     //statsgw_last_up = up;
121 
122 
123     char bodystr[102400];
124     strcpy(bodystr,"");
125     strcat(bodystr,top_page);
126 
127     tdlist_t tds = swift::GetTransferDescriptors();
128     tdlist_t::iterator iter;
129     for (iter = tds.begin(); iter != tds.end(); iter++) {
130         int td = *iter;
131         uint64_t total = (int)swift::Size(td);
132         uint64_t down  = (int)swift::Complete(td);
133         int perc = (int)((down * 100) / total);
134 
135         char roothashhexstr[1024];
136         sprintf(roothashhexstr,"%s", GetSwarmID(td).hex().c_str());
137 
138         char templ[2048];
139         sprintf(templ,swarm_page_templ,roothashhexstr, perc, '%', dspeed, uspeed);
140         strcat(bodystr,templ);
141     }
142 
143     strcat(bodystr,bottom_page);
144 
145     char contlenstr[1024];
146     sprintf(contlenstr,PRISIZET,strlen(bodystr));
147     struct evkeyvalq *headers = evhttp_request_get_output_headers(evreq);
148     evhttp_add_header(headers, "Connection", "close");
149     evhttp_add_header(headers, "Content-Type", "text/html");
150     evhttp_add_header(headers, "Content-Length", contlenstr);
151     evhttp_add_header(headers, "Accept-Ranges", "none");
152 
153     // Construct evbuffer and send via chunked encoding
154     struct evbuffer *evb = evbuffer_new();
155     int ret = evbuffer_add(evb,bodystr,strlen(bodystr));
156     if (ret < 0) {
157         print_error("statsgw: OverviewCallback: error evbuffer_add");
158         return;
159     }
160 
161     evhttp_send_reply(evreq, 200, "OK", evb);
162     evbuffer_free(evb);
163 }
164 
165 
StatsGetSpeedCallback(struct evhttp_request * evreq)166 void StatsGetSpeedCallback(struct evhttp_request *evreq)
167 {
168     if (statsgw_last_time == 0) {
169         statsgw_last_time = NOW-1000000;
170     }
171 
172     tint nu = Channel::Time();
173     uint64_t down = Channel::global_raw_bytes_down;
174     uint64_t up = Channel::global_raw_bytes_up;
175 
176     int dspeed = 0, uspeed = 0;
177     tint tdiff = (nu - statsgw_last_time)/1000000;
178     if (tdiff > 0) {
179         dspeed = (int)(((down-statsgw_last_down)/1024) / tdiff);
180         uspeed = (int)(((up-statsgw_last_up)/1024) / tdiff);
181     }
182     statsgw_last_down = down;
183     statsgw_last_up = up;
184     statsgw_last_time = nu;
185 
186     // Arno: PDD+ wants content speeds too
187     double contentdownspeed = 0.0, contentupspeed = 0.0;
188     uint32_t nleech=0,nseed=0;
189 
190     tdlist_t tds = swift::GetTransferDescriptors();
191     tdlist_t::iterator iter;
192     for (iter = tds.begin(); iter != tds.end(); iter++) {
193         int td = *iter;
194         contentdownspeed += swift::GetCurrentSpeed(td,DDIR_DOWNLOAD);
195         contentupspeed += swift::GetCurrentSpeed(td,DDIR_UPLOAD);
196         nleech += swift::GetNumLeechers(td);
197         nseed += swift::GetNumSeeders(td);
198         // TODO: Are these active leechers and seeders, or potential seeders and leechers? In the latter case these can be retrieved when cached peers are implemented
199     }
200     int cdownspeed = (int)(contentdownspeed/1024.0);
201     int cupspeed = (int)(contentupspeed/1024.0);
202 
203     char speedstr[1024];
204     sprintf(speedstr,
205             "{\"downspeed\": %d, \"success\": \"true\", \"upspeed\": %d, \"cdownspeed\": %d, \"cupspeed\": %d, \"nleech\": %d, \"nseed\": %d}",
206             dspeed, uspeed, cdownspeed, cupspeed, nleech, nseed);
207 
208     char contlenstr[1024];
209     sprintf(contlenstr,PRISIZET,strlen(speedstr));
210     struct evkeyvalq *headers = evhttp_request_get_output_headers(evreq);
211     evhttp_add_header(headers, "Connection", "close");
212     evhttp_add_header(headers, "Content-Type", "application/json");
213     evhttp_add_header(headers, "Content-Length", contlenstr);
214     evhttp_add_header(headers, "Accept-Ranges", "none");
215 
216     // Construct evbuffer and send via chunked encoding
217     struct evbuffer *evb = evbuffer_new();
218     int ret = evbuffer_add(evb,speedstr,strlen(speedstr));
219     if (ret < 0) {
220         print_error("statsgw: GetSpeedCallback: error evbuffer_add");
221         return;
222     }
223 
224     evhttp_send_reply(evreq, 200, "OK", evb);
225     evbuffer_free(evb);
226 }
227 
228 
StatsGwNewRequestCallback(struct evhttp_request * evreq,void * arg)229 void StatsGwNewRequestCallback(struct evhttp_request *evreq, void *arg)
230 {
231 
232     dprintf("%s @%i http new request\n",tintstr(),statsgw_reqs_count);
233     statsgw_reqs_count++;
234 
235     if (evhttp_request_get_command(evreq) != EVHTTP_REQ_GET) {
236         return;
237     }
238 
239     // Parse URI
240     const char *uri = evhttp_request_get_uri(evreq);
241     //struct evkeyvalq *headers =   evhttp_request_get_input_headers(evreq);
242     //const char *contentrangestr =evhttp_find_header(headers,"Content-Range");
243 
244     fprintf(stderr,"statsgw: GOT %s\n", uri);
245 
246     if (strstr(uri,"get_speed_info") != NULL) {
247         StatsGetSpeedCallback(evreq);
248     } else if (!strncmp(uri,"/webUI/exit",strlen("/webUI/exit")) || statsgw_quit_process) {
249         statsgw_quit_process = true;
250         StatsExitCallback(evreq);
251     } else if (!strncmp(uri,"/webUI",strlen("/webUI"))) {
252         StatsOverviewCallback(evreq);
253     }
254 }
255 
256 
InstallStatsGateway(struct event_base * evbase,Address bindaddr)257 bool InstallStatsGateway(struct event_base *evbase,Address bindaddr)
258 {
259     // Arno, 2011-10-04: From libevent's http-server.c example
260 
261     /* Create a new evhttp object to handle requests. */
262     statsgw_event = evhttp_new(evbase);
263     if (!statsgw_event) {
264         print_error("statsgw: evhttp_new failed");
265         return false;
266     }
267 
268     /* Install callback for all requests */
269     evhttp_set_gencb(statsgw_event, StatsGwNewRequestCallback, NULL);
270 
271     /* Now we tell the evhttp what port to listen on */
272     statsgw_handle = evhttp_bind_socket_with_handle(statsgw_event, bindaddr.ipstr().c_str(), bindaddr.port());
273     if (!statsgw_handle) {
274         print_error("statsgw: evhttp_bind_socket_with_handle failed");
275         return false;
276     }
277 
278     return true;
279 }
280