1
function dragstarted(d) {
2
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
12
function dragended(d) {
13
if (!d3.event.active) simulation.alphaTarget(0);
18
var origins = new Set([
19
'ffed335bc92f6f27d33a8b5a12866328b1e70117',
20
'598bd849259c97118c11759b9895fda171610a51',
21
'cd178baaa144a3c86c7ee9080936c73da8b4b597',
24
const bitfieldWidth = 200;
28
function bitfieldString(bitfield) {
31
for (var i = 0; i < bitfield.length; i++) {
32
if (i > 0 && i % bitfieldWidth == 0) {
35
if (rows.length == cursor) {
38
rows[cursor].push(bitfield[i]);
40
return 'Pieces: ' + rows.map(row => row.map(b => b ? '1' : '0').join('')).join('\n');
43
function connKey(sourceID, targetID) {
44
if (sourceID < targetID) {
45
return sourceID + ':' + targetID;
47
return targetID + ':' + sourceID;
51
constructor(torrent, startTime) {
52
this.torrent = torrent;
53
this.startTime = startTime;
54
this.curTime = startTime;
56
this.w = window.innerWidth;
57
this.h = window.innerHeight - 120;
59
this.header = d3.select('#graph').append('div').attr('class', 'header');
60
this.headerTime = this.header.append('p').append('pre');
61
this.headerElapsed = this.header.append('p').append('pre').text('Elapsed: 0s');
62
this.headerNumPeers = this.header.append('p').append('pre');
63
this.headerTorrent = this.header.append('p').append('pre').text('Torrent: ' + torrent);
64
this.headerPeerID = this.header.append('p').append('pre');
65
this.headerBitfield = this.header.append('p').append('pre');
67
this.svg = d3.select('#graph').append('svg')
68
.attr('width', this.w)
69
.attr('height', this.h);
71
this.svg.append('g').attr('class', 'links');
73
this.svg.append('g').attr('class', 'blinks');
75
this.svg.append('g').attr('class', 'nodes');
81
this.peerIndexByID = {};
83
this.connKeys = new Set();
85
this.simulation = d3.forceSimulation(this.peers)
86
.force('charge', d3.forceManyBody().strength(-70).distanceMax(400))
87
.force('center', d3.forceCenter(this.w/2, this.h/2))
88
.force('link', d3.forceLink(this.conns).id(d => d.id));
92
this.currHighlightedPeer = null;
94
this.simulation.on('tick', this.tick.bind(this));
98
if (!(id in this.peerIndexByID)) {
100
message: 'not found',
108
return this.peers[this.peerIndexByID[id]];
112
this.simulation.nodes(this.peers);
113
this.simulation.force('link').links(this.conns);
118
for (var i = this.blacklist.length - 1; i >= 0; i--) {
119
if (this.blacklist[i].expiresAt < this.curTime) {
120
this.blacklist.splice(i, 1);
124
this.blink = this.svg.select('.blinks').selectAll('.blink').data(this.blacklist);
129
.attr('class', 'blink')
130
.attr('stroke-width', 1)
131
.attr('stroke', '#ef9d88');
133
this.blink.exit().remove();
137
this.link = this.svg.select('.links').selectAll('.link').data(this.conns);
142
.attr('class', 'link')
143
.attr('stroke-width', 1)
144
.attr('stroke', '#999999');
146
this.link.exit().remove();
150
this.node = this.svg.select('.nodes').selectAll('.node').data(this.peers);
154
if (!d3.event.active) this.simulation.alphaTarget(0.3).restart();
163
if (!d3.event.active) this.simulation.alphaTarget(0);
171
.attr('class', 'node')
173
.attr('stroke-width', 1.5)
176
this.headerPeerID.text('PeerID: ' + d.id);
177
this.headerBitfield.text(bitfieldString(d.bitfield));
178
if (this.currHighlightedPeer) {
179
this.currHighlightedPeer.highlight = false;
181
this.currHighlightedPeer = d;
183
this.node.attr('id', d => d.highlight ? 'highlight' : null);
184
this.blacklist = d.blacklist;
190
if (origins.has(d.id)) {
191
return 'hsl(230, 100%, 50%)';
194
return 'hsl(120, 100%, 50%)';
197
d.bitfield.forEach(b => completed += b ? 1 : 0);
198
var percentComplete = 100.0 * completed / d.bitfield.length;
199
return 'hsl(55, ' + Math.ceil(percentComplete) + '%, 50%)';
203
this.headerBitfield.text(bitfieldString(d.bitfield));
207
this.node.exit().remove();
209
this.simulation.alphaTarget(0.05).restart();
212
addPeer(id, bitfield) {
213
if (id in this.peerIndexByID) {
215
message: 'duplicate peer',
219
this.peerIndexByID[id] = this.peers.length;
230
this.headerNumPeers.text('Num peers: ' + this.peers.length);
233
addActiveConn(sourceID, targetID) {
234
this.checkPeer(sourceID);
235
this.checkPeer(targetID);
236
var k = connKey(sourceID, targetID);
237
if (this.connKeys.has(k)) {
248
removeActiveConn(sourceID, targetID) {
249
var k = connKey(sourceID, targetID);
250
if (!this.connKeys.has(k)) {
254
for (var i = 0; i < this.conns.length; i++) {
255
var curK = connKey(this.conns[i].source.id, this.conns[i].target.id);
257
this.conns.splice(i, 1);
263
blacklistConn(sourceID, targetID, duration) {
264
var source = this.getPeer(sourceID);
265
var target = this.getPeer(targetID);
266
source.blacklist.push({
269
expiresAt: this.curTime + duration,
273
receivePiece(id, piece) {
274
this.getPeer(id).bitfield[piece] = true;
278
var p = this.getPeer(id);
280
for (var i = 0; i < p.bitfield.length; i++) {
281
p.bitfield[i] = true;
288
d.x = Math.max(radius, Math.min(this.w - radius, d.x));
292
d.y = Math.max(radius, Math.min(this.h - radius, d.y));
297
.attr('x1', d => d.source.x)
298
.attr('y1', d => d.source.y)
299
.attr('x2', d => d.target.x)
300
.attr('y2', d => d.target.y);
303
.attr('x1', d => d.source.x)
304
.attr('y1', d => d.source.y)
305
.attr('x2', d => d.target.x)
306
.attr('y2', d => d.target.y);
311
this.headerTime.text(d.toString());
312
var elapsed = (t - this.startTime) / 1000;
313
this.headerElapsed.text('Elapsed: ' + elapsed + 's');
318
d3.request('http://' + location.host + '/events').get(req => {
319
var events = JSON.parse(req.response);
320
var graph = new Graph(events[0].torrent, Date.parse(events[0].ts));
325
var earlyEvents = {};
327
function applyEvent(event) {
329
switch (event.event) {
331
graph.addPeer(event.self, event.bitfield);
332
if (event.self in earlyEvents) {
333
earlyEvents[event.self].forEach(e => applyEvent(e))
336
case 'add_active_conn':
337
graph.addActiveConn(event.self, event.peer);
339
case 'drop_active_conn':
340
graph.removeActiveConn(event.self, event.peer);
342
case 'receive_piece':
343
graph.receivePiece(event.self, event.piece);
345
case 'torrent_complete':
346
graph.completePeer(event.self);
348
case 'blacklist_conn':
349
graph.blacklistConn(event.self, event.peer, parseInt(event.duration_ms));
353
if (err.message == 'not found') {
354
if (!(err.peer in earlyEvents)) {
355
earlyEvents[err.peer] = [];
357
earlyEvents[err.peer].push(event);
359
console.log('unhandled error: ' + err);
367
const interval = 100;
369
function readEvents(i, until) {
370
if (i >= events.length) {
373
graph.setTime(until);
374
while (i < events.length && Date.parse(events[i].ts) < until) {
375
applyEvent(events[i]);
379
setTimeout(() => readEvents(i, until + interval), interval);
382
readEvents(0, Date.parse(events[0].ts) + interval);