kraken

Форк
0
383 строки · 9.8 Кб
1
function dragstarted(d) {
2
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
3
  d.fx = d.x;
4
  d.fy = d.y;
5
}
6

7
function dragged(d) {
8
  d.fx = d3.event.x;
9
  d.fy = d3.event.y;
10
}
11

12
function dragended(d) {
13
  if (!d3.event.active) simulation.alphaTarget(0);
14
  d.fx = null;
15
  d.fy = null;
16
}
17

18
var origins = new Set([
19
  'ffed335bc92f6f27d33a8b5a12866328b1e70117',
20
  '598bd849259c97118c11759b9895fda171610a51',
21
  'cd178baaa144a3c86c7ee9080936c73da8b4b597',
22
])
23

24
const bitfieldWidth = 200;
25

26
const radius = 8;
27

28
function bitfieldString(bitfield) {
29
  var rows = [];
30
  var cursor = 0;
31
  for (var i = 0; i < bitfield.length; i++) {
32
    if (i > 0 && i % bitfieldWidth == 0) {
33
      cursor++;
34
    }
35
    if (rows.length == cursor) {
36
      rows.push([]);
37
    }
38
    rows[cursor].push(bitfield[i]);
39
  }
40
  return 'Pieces: ' + rows.map(row => row.map(b => b ? '1' : '0').join('')).join('\n');
41
}
42

43
function connKey(sourceID, targetID) {
44
  if (sourceID < targetID) {
45
    return sourceID + ':' + targetID;
46
  }
47
  return targetID + ':' + sourceID;
48
}
49

50
class Graph {
51
  constructor(torrent, startTime) {
52
    this.torrent = torrent;
53
    this.startTime = startTime;
54
    this.curTime = startTime;
55

56
    this.w = window.innerWidth;
57
    this.h = window.innerHeight - 120;
58

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');
66

67
    this.svg = d3.select('#graph').append('svg')
68
      .attr('width', this.w)
69
      .attr('height', this.h);
70

71
    this.svg.append('g').attr('class', 'links');
72

73
    this.svg.append('g').attr('class', 'blinks');
74

75
    this.svg.append('g').attr('class', 'nodes');
76

77
    this.peers = [];
78
    this.conns = [];
79
    this.blacklist = [];
80

81
    this.peerIndexByID = {};
82

83
    this.connKeys = new Set();
84

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));
89

90
    this.update();
91

92
    this.currHighlightedPeer = null;
93

94
    this.simulation.on('tick', this.tick.bind(this));
95
  }
96

97
  checkPeer(id) {
98
    if (!(id in this.peerIndexByID)) {
99
      throw {
100
        message: 'not found',
101
        peer: id,
102
      }
103
    }
104
  }
105

106
  getPeer(id) {
107
    this.checkPeer(id);
108
    return this.peers[this.peerIndexByID[id]];
109
  }
110

111
  update() {
112
    this.simulation.nodes(this.peers);
113
    this.simulation.force('link').links(this.conns);
114

115
    // Draw blacklisted connection links.
116

117
    // Remove expired blacklist items. Loop backwards so splice still works.
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);
121
      }
122
    }
123

124
    this.blink = this.svg.select('.blinks').selectAll('.blink').data(this.blacklist);
125

126
    this.blink
127
      .enter()
128
      .append('line')
129
      .attr('class', 'blink')
130
      .attr('stroke-width', 1)
131
      .attr('stroke', '#ef9d88');
132

133
    this.blink.exit().remove();
134

135
    // Draw connection links.
136

137
    this.link = this.svg.select('.links').selectAll('.link').data(this.conns);
138

139
    this.link
140
      .enter()
141
      .append('line')
142
      .attr('class', 'link')
143
      .attr('stroke-width', 1)
144
      .attr('stroke', '#999999');
145

146
    this.link.exit().remove();
147

148
    // Draw peer nodes.
149

150
    this.node = this.svg.select('.nodes').selectAll('.node').data(this.peers);
151

152
    var drag = d3.drag()
153
      .on('start', d => {
154
        if (!d3.event.active) this.simulation.alphaTarget(0.3).restart();
155
        d.fx = d.x;
156
        d.fy = d.y;
157
      })
158
      .on('drag', d => {
159
        d.fx = d3.event.x;
160
        d.fy = d3.event.y;
161
      })
162
      .on('end', d => {
163
        if (!d3.event.active) this.simulation.alphaTarget(0);
164
        d.fx = null;
165
        d.fy = null;
166
      })
167

168
    this.node
169
      .enter()
170
      .append('circle')
171
      .attr('class', 'node')
172
      .attr('r', radius)
173
      .attr('stroke-width', 1.5)
174
      .call(drag)
175
      .on('click', d => {
176
        this.headerPeerID.text('PeerID: ' + d.id);
177
        this.headerBitfield.text(bitfieldString(d.bitfield));
178
        if (this.currHighlightedPeer) {
179
          this.currHighlightedPeer.highlight = false;
180
        }
181
        this.currHighlightedPeer = d;
182
        d.highlight = true;
183
        this.node.attr('id', d => d.highlight ? 'highlight' : null);
184
        this.blacklist = d.blacklist;
185
        this.update();
186
      });
187

188
    this.node
189
      .attr('fill', d => {
190
        if (origins.has(d.id)) {
191
          return 'hsl(230, 100%, 50%)';
192
        }
193
        if (d.complete) {
194
          return 'hsl(120, 100%, 50%)';
195
        }
196
        var completed = 0;
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%)';
200
      })
201
      .each(d => {
202
        if (d.highlight) {
203
          this.headerBitfield.text(bitfieldString(d.bitfield));
204
        }
205
      });
206

207
    this.node.exit().remove();
208

209
    this.simulation.alphaTarget(0.05).restart();
210
  }
211

212
  addPeer(id, bitfield) {
213
    if (id in this.peerIndexByID) {
214
      throw {
215
        message: 'duplicate peer',
216
        peer: id,
217
      }
218
    }
219
    this.peerIndexByID[id] = this.peers.length;
220
    this.peers.push({
221
      type: 'peer',
222
      id: id,
223
      x: this.w / 2,
224
      y: this.h / 2,
225
      complete: false,
226
      bitfield: bitfield,
227
      highlight: false,
228
      blacklist: [],
229
    });
230
    this.headerNumPeers.text('Num peers: ' + this.peers.length);
231
  }
232

233
  addActiveConn(sourceID, targetID) {
234
    this.checkPeer(sourceID);
235
    this.checkPeer(targetID);
236
    var k = connKey(sourceID, targetID);
237
    if (this.connKeys.has(k)) {
238
      return;
239
    }
240
    this.conns.push({
241
      type: 'conn',
242
      source: sourceID,
243
      target: targetID,
244
    });
245
    this.connKeys.add(k)
246
  }
247

248
  removeActiveConn(sourceID, targetID) {
249
    var k = connKey(sourceID, targetID);
250
    if (!this.connKeys.has(k)) {
251
      return;
252
    }
253
    var removed = false;
254
    for (var i = 0; i < this.conns.length; i++) {
255
      var curK = connKey(this.conns[i].source.id, this.conns[i].target.id);
256
      if (curK == k) {
257
        this.conns.splice(i, 1);
258
        removed = true;
259
      }
260
    }
261
  }
262

263
  blacklistConn(sourceID, targetID, duration) {
264
    var source = this.getPeer(sourceID);
265
    var target = this.getPeer(targetID);
266
    source.blacklist.push({
267
      source: source,
268
      target: target,
269
      expiresAt: this.curTime + duration,
270
    })
271
  }
272

273
  receivePiece(id, piece) {
274
    this.getPeer(id).bitfield[piece] = true;
275
  }
276

277
  completePeer(id) {
278
    var p = this.getPeer(id);
279
    p.complete = true;
280
    for (var i = 0; i < p.bitfield.length; i++) {
281
      p.bitfield[i] = true;
282
    }
283
  }
284

285
  tick() {
286
    this.node
287
      .attr('cx', d => {
288
        d.x = Math.max(radius, Math.min(this.w - radius, d.x));
289
        return d.x;
290
      })
291
      .attr('cy', d => {
292
        d.y = Math.max(radius, Math.min(this.h - radius, d.y));
293
        return d.y;
294
      });
295

296
    this.link
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);
301

302
    this.blink
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);
307
  }
308

309
  setTime(t) {
310
    var d = new Date(t);
311
    this.headerTime.text(d.toString());
312
    var elapsed = (t - this.startTime) / 1000;
313
    this.headerElapsed.text('Elapsed: ' + elapsed + 's');
314
    this.curTime = t;
315
  }
316
}
317

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));
321

322
  // Maps peer id to list of events which occurred before the peer was added
323
  // to the graph. Early events are possible in cases where a connection is
324
  // added before the torrent is opened, which is valid.
325
  var earlyEvents = {};
326

327
  function applyEvent(event) {
328
    try {
329
      switch (event.event) {
330
        case 'add_torrent':
331
          graph.addPeer(event.self, event.bitfield);
332
          if (event.self in earlyEvents) {
333
            earlyEvents[event.self].forEach(e => applyEvent(e))
334
          }
335
          break;
336
        case 'add_active_conn':
337
          graph.addActiveConn(event.self, event.peer);
338
          break;
339
        case 'drop_active_conn':
340
          graph.removeActiveConn(event.self, event.peer);
341
          break;
342
        case 'receive_piece':
343
          graph.receivePiece(event.self, event.piece);
344
          break;
345
        case 'torrent_complete':
346
          graph.completePeer(event.self);
347
          break;
348
        case 'blacklist_conn':
349
          graph.blacklistConn(event.self, event.peer, parseInt(event.duration_ms));
350
          break;
351
      }
352
    } catch (err) {
353
      if (err.message == 'not found') {
354
        if (!(err.peer in earlyEvents)) {
355
          earlyEvents[err.peer] = [];
356
        }
357
        earlyEvents[err.peer].push(event);
358
      } else {
359
        console.log('unhandled error: ' + err);
360
      }
361
    }
362
  }
363

364
  // Every interval seconds, we read all events that occur within that interval
365
  // and apply them to the graph. This gives the illusion of events occuring in
366
  // real-time.
367
  const interval = 100;
368

369
  function readEvents(i, until) {
370
    if (i >= events.length) {
371
      return;
372
    }
373
    graph.setTime(until);
374
    while (i < events.length && Date.parse(events[i].ts) < until) {
375
      applyEvent(events[i]);
376
      i++;
377
    }
378
    graph.update();
379
    setTimeout(() => readEvents(i, until + interval), interval);
380
  }
381

382
  readEvents(0, Date.parse(events[0].ts) + interval);
383
});
384

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.