package org.gudy.azureus2.ui.swt.views.clientstats; import java.net.InetAddress; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.*; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; import org.gudy.azureus2.core3.download.*; import org.gudy.azureus2.core3.global.GlobalManagerListener; import org.gudy.azureus2.core3.peer.PEPeer; import org.gudy.azureus2.core3.peer.PEPeerListener; import org.gudy.azureus2.core3.peer.PEPeerManager; import org.gudy.azureus2.core3.util.*; import org.gudy.azureus2.plugins.ui.UIManager; import org.gudy.azureus2.plugins.ui.tables.TableColumn; import org.gudy.azureus2.plugins.ui.tables.TableColumnCreationListener; import org.gudy.azureus2.plugins.ui.tables.TableManager; import org.gudy.azureus2.pluginsimpl.local.PluginInitializer; import org.gudy.azureus2.ui.swt.Utils; import org.gudy.azureus2.ui.swt.mainwindow.ClipboardCopy; import org.gudy.azureus2.ui.swt.views.table.TableViewSWT; import org.gudy.azureus2.ui.swt.views.table.impl.TableViewSWTImpl; import org.gudy.azureus2.ui.swt.views.table.impl.TableViewTab; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.AzureusCoreFactory; import com.aelitis.azureus.core.AzureusCoreRunningListener; import com.aelitis.azureus.core.peermanager.piecepicker.util.BitFlags; import com.aelitis.azureus.core.util.bloom.BloomFilter; import com.aelitis.azureus.core.util.bloom.BloomFilterFactory; import com.aelitis.azureus.ui.common.table.TableColumnCore; import com.aelitis.azureus.ui.common.table.TableLifeCycleListener; import com.aelitis.azureus.ui.common.table.TableRowCore; import com.aelitis.azureus.ui.common.table.impl.TableColumnManager; import com.aelitis.azureus.util.MapUtils; public class ClientStatsView extends TableViewTab<ClientStatsDataSource> implements TableLifeCycleListener, GlobalManagerListener, DownloadManagerPeerListener { private static final String CONFIG_FILE = "ClientStats.dat"; private static final String CONFIG_FILE_ARCHIVE = "ClientStats_%1.dat"; private static final int BLOOMFILTER_SIZE = 100000; private static final int BLOOMFILTER_PEERID_SIZE = 50000; private static final String TABLEID = "ClientStats"; private AzureusCore core; private TableViewSWTImpl<ClientStatsDataSource> tv; private boolean columnsAdded; private Map<String, ClientStatsDataSource> mapData; private Composite parent; private BloomFilter bloomFilter; private BloomFilter bloomFilterPeerId; private ClientStatsOverall overall; private long startedListeningOn; private long totalTime; private long lastAdd; private GregorianCalendar calendar = new GregorianCalendar(); private int lastAddMonth; public ClientStatsView() { super("ClientStats"); initAndLoad(); AzureusCoreFactory.addCoreRunningListener(new AzureusCoreRunningListener() { public void azureusCoreRunning(AzureusCore core) { initColumns(core); } }); } public Composite initComposite(Composite composite) { parent = new Composite(composite, SWT.BORDER); parent.setLayout(new FormLayout()); Layout layout = composite.getLayout(); if (layout instanceof GridLayout) { parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); } else if (layout instanceof FormLayout) { parent.setLayoutData(Utils.getFilledFormData()); } return parent; } public void tableViewTabInitComplete() { Composite cTV = (Composite) parent.getChildren()[0]; Composite cBottom = new Composite(parent, SWT.None); FormData fd; fd = Utils.getFilledFormData(); fd.bottom = new FormAttachment(cBottom); cTV.setLayoutData(fd); fd = Utils.getFilledFormData(); fd.top = null; cBottom.setLayoutData(fd); cBottom.setLayout(new FormLayout()); Button btnCopy = new Button(cBottom, SWT.PUSH); btnCopy.setLayoutData(new FormData()); btnCopy.setText("Copy"); btnCopy.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event) { TableRowCore[] rows = tv.getRows(); StringBuilder sb = new StringBuilder(); sb.append(new SimpleDateFormat("MMM yyyy").format(new Date())); sb.append("\n"); sb.append("Hits,Client,Bytes Sent,Bytes Received,Bad Bytes\n"); for (TableRowCore row : rows) { ClientStatsDataSource stat = (ClientStatsDataSource) row.getDataSource(); if (stat == null) { continue; } sb.append(stat.count); sb.append(","); sb.append(stat.client.replaceAll(",", "")); sb.append(","); sb.append(stat.bytesSent); sb.append(","); sb.append(stat.bytesReceived); sb.append(","); sb.append(stat.bytesDiscarded); sb.append("\n"); } ClipboardCopy.copyToClipBoard(sb.toString()); } }); Button btnCopyShort = new Button(cBottom, SWT.PUSH); btnCopyShort.setLayoutData(new FormData()); btnCopyShort.setText("Copy > 1%"); btnCopyShort.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event) { StringBuilder sb = new StringBuilder(); sb.append(new SimpleDateFormat("MMM ''yy").format(new Date())); sb.append("] "); sb.append(overall.count); sb.append(": "); ClientStatsDataSource[] stats = mapData.values().toArray( new ClientStatsDataSource[0]); Arrays.sort(stats, new Comparator<ClientStatsDataSource>() { public int compare(ClientStatsDataSource o1, ClientStatsDataSource o2) { if (o1.count == o2.count) { return 0; } return o1.count > o2.count ? -1 : 1; } }); boolean first = true; for (ClientStatsDataSource stat : stats) { int pct = (int) (stat.count * 1000 / overall.count); if (pct < 10) { continue; } if (first) { first = false; } else { sb.append(", "); } sb.append(DisplayFormatters.formatPercentFromThousands(pct)); sb.append(" "); sb.append(stat.client); } Arrays.sort(stats, new Comparator<ClientStatsDataSource>() { public int compare(ClientStatsDataSource o1, ClientStatsDataSource o2) { float v1 = (float) o1.bytesReceived / o1.count; float v2 = (float) o2.bytesReceived / o2.count; if (v1 == v2) { return 0; } return v1 > v2 ? -1 : 1; } }); /* int top = 5; first = true; sb.append("\nBest Seeders ("); long total = 0; for (ClientStatsDataSource stat : stats) { total += stat.bytesReceived; } sb.append(DisplayFormatters.formatByteCountToKiBEtc(total, false, true, 0)); sb.append(" Downloaded): "); for (ClientStatsDataSource stat : stats) { if (first) { first = false; } else { sb.append(", "); } sb.append(DisplayFormatters.formatByteCountToKiBEtc( stat.bytesReceived / stat.count, false, true, 0)); sb.append(" per "); sb.append(stat.client); sb.append("(x"); sb.append(stat.count); sb.append(")"); if (--top <= 0) { break; } } Arrays.sort(stats, new Comparator<ClientStatsDataSource>() { public int compare(ClientStatsDataSource o1, ClientStatsDataSource o2) { float v1 = (float) o1.bytesDiscarded / o1.count; float v2 = (float) o2.bytesDiscarded / o2.count; if (v1 == v2) { return 0; } return v1 > v2 ? -1 : 1; } }); top = 5; first = true; sb.append("\nMost Discarded ("); total = 0; for (ClientStatsDataSource stat : stats) { total += stat.bytesDiscarded; } sb.append(DisplayFormatters.formatByteCountToKiBEtc(total, false, true, 0)); sb.append(" Discarded): "); for (ClientStatsDataSource stat : stats) { if (first) { first = false; } else { sb.append(", "); } sb.append(DisplayFormatters.formatByteCountToKiBEtc( stat.bytesDiscarded / stat.count, false, true, 0)); sb.append(" per "); sb.append(stat.client); sb.append("(x"); sb.append(stat.count); sb.append(")"); if (--top <= 0) { break; } } Arrays.sort(stats, new Comparator<ClientStatsDataSource>() { public int compare(ClientStatsDataSource o1, ClientStatsDataSource o2) { float v1 = (float) o1.bytesSent / o1.count; float v2 = (float) o2.bytesSent / o2.count; if (v1 == v2) { return 0; } return v1 > v2 ? -1 : 1; } }); top = 5; first = true; sb.append("\nMost Fed ("); total = 0; for (ClientStatsDataSource stat : stats) { total += stat.bytesSent; } sb.append(DisplayFormatters.formatByteCountToKiBEtc(total, false, true, 0)); sb.append(" Sent): "); for (ClientStatsDataSource stat : stats) { if (first) { first = false; } else { sb.append(", "); } sb.append(DisplayFormatters.formatByteCountToKiBEtc( stat.bytesSent / stat.count, false, true, 0)); sb.append(" per "); sb.append(stat.client); sb.append("(x"); sb.append(stat.count); sb.append(")"); if (--top <= 0) { break; } } */ ClipboardCopy.copyToClipBoard(sb.toString()); } }); fd = new FormData(); fd.left = new FormAttachment(btnCopy, 5); btnCopyShort.setLayoutData(fd); } public TableViewSWT<ClientStatsDataSource> initYourTableView() { tv = new TableViewSWTImpl<ClientStatsDataSource>( ClientStatsDataSource.class, TABLEID, getPropertiesPrefix(), new TableColumnCore[0], ColumnCS_Count.COLUMN_ID, SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL); /* tv.addTableDataSourceChangedListener(this, true); tv.addRefreshListener(this, true); tv.addSelectionListener(this, false); tv.addMenuFillListener(this); */ tv.addLifeCycleListener(this); return tv; } private void initColumns(AzureusCore core) { synchronized (ClientStatsView.class) { if (columnsAdded) { return; } columnsAdded = true; } UIManager uiManager = PluginInitializer.getDefaultInterface().getUIManager(); TableManager tableManager = uiManager.getTableManager(); tableManager.registerColumn(ClientStatsDataSource.class, ColumnCS_Name.COLUMN_ID, new TableColumnCreationListener() { public void tableColumnCreated(TableColumn column) { new ColumnCS_Name(column); } }); tableManager.registerColumn(ClientStatsDataSource.class, ColumnCS_Count.COLUMN_ID, new TableColumnCreationListener() { public void tableColumnCreated(TableColumn column) { new ColumnCS_Count(column); } }); /* tableManager.registerColumn(ClientStatsDataSource.class, ColumnCS_Discarded.COLUMN_ID, new TableColumnCreationListener() { public void tableColumnCreated(TableColumn column) { new ColumnCS_Discarded(column); } }); tableManager.registerColumn(ClientStatsDataSource.class, ColumnCS_Received.COLUMN_ID, new TableColumnCreationListener() { public void tableColumnCreated(TableColumn column) { new ColumnCS_Received(column); } }); tableManager.registerColumn(ClientStatsDataSource.class, ColumnCS_ReceivedPer.COLUMN_ID, new TableColumnCreationListener() { public void tableColumnCreated(TableColumn column) { new ColumnCS_ReceivedPer(column); } }); tableManager.registerColumn(ClientStatsDataSource.class, ColumnCS_Sent.COLUMN_ID, new TableColumnCreationListener() { public void tableColumnCreated(TableColumn column) { new ColumnCS_Sent(column); } }); */ tableManager.registerColumn(ClientStatsDataSource.class, ColumnCS_Pct.COLUMN_ID, new TableColumnCreationListener() { public void tableColumnCreated(TableColumn column) { new ColumnCS_Pct(column); } }); TableColumnManager tcManager = TableColumnManager.getInstance(); tcManager.setDefaultColumnNames(TABLEID, new String[] { ColumnCS_Name.COLUMN_ID, ColumnCS_Pct.COLUMN_ID, ColumnCS_Count.COLUMN_ID, /* ColumnCS_Received.COLUMN_ID, ColumnCS_Sent.COLUMN_ID, ColumnCS_Discarded.COLUMN_ID, */ }); } public void tableViewDestroyed() { if (core == null) { // not initialized, skip save return; } core.getGlobalManager().removeListener(this); List downloadManagers = core.getGlobalManager().getDownloadManagers(); for (Object object : downloadManagers) { ((DownloadManager) object).removePeerListener(this); } save(CONFIG_FILE); } private void initAndLoad() { mapData = new HashMap<String, ClientStatsDataSource>(); synchronized (mapData) { Map map = FileUtil.readResilientConfigFile(CONFIG_FILE); totalTime = MapUtils.getMapLong(map, "time", 0); lastAdd = MapUtils.getMapLong(map, "lastadd", 0); if (lastAdd != 0) { calendar.setTimeInMillis(lastAdd); lastAddMonth = calendar.get(Calendar.MONTH); Map mapBloom = MapUtils.getMapMap(map, "bloomfilter", null); if (mapBloom != null) { bloomFilter = BloomFilterFactory.deserialiseFromMap(mapBloom); } mapBloom = MapUtils.getMapMap(map, "bloomfilterPeerId", null); if (mapBloom != null) { bloomFilterPeerId = BloomFilterFactory.deserialiseFromMap(mapBloom); } } if (bloomFilter == null) { bloomFilter = BloomFilterFactory.createRotating( BloomFilterFactory.createAddOnly(BLOOMFILTER_SIZE), 2); } if (bloomFilterPeerId == null) { bloomFilterPeerId = BloomFilterFactory.createRotating( BloomFilterFactory.createAddOnly(BLOOMFILTER_PEERID_SIZE), 2); } overall = new ClientStatsOverall(); List listSavedData = MapUtils.getMapList(map, "data", null); if (listSavedData != null) { for (Object val : listSavedData) { try { Map mapVal = (Map) val; if (mapVal != null) { ClientStatsDataSource ds = new ClientStatsDataSource(mapVal); ds.overall = overall; if (!mapData.containsKey(ds.client)) { mapData.put(ds.client, ds); overall.count += ds.count; } } } catch (Exception e) { // ignore } } } } } private void save(String filename) { Map<String, Object> map = new HashMap<String, Object>(); synchronized (mapData) { map.put("data", new ArrayList(mapData.values())); map.put("bloomfilter", bloomFilter.serialiseToMap()); map.put("bloomfilterPeerId", bloomFilterPeerId.serialiseToMap()); map.put("lastadd", SystemTime.getCurrentTime()); if (startedListeningOn > 0) { map.put("time", totalTime + (SystemTime.getCurrentTime() - startedListeningOn)); } else { map.put("time", totalTime); } } FileUtil.writeResilientConfigFile(filename, map); } public void tableViewInitialized() { synchronized (mapData) { if (mapData.values().size() > 0) { tv.addDataSources(mapData.values().toArray(new ClientStatsDataSource[0])); } } AzureusCoreFactory.addCoreRunningListener(new AzureusCoreRunningListener() { public void azureusCoreRunning(AzureusCore core) { register(core); } }); } protected void register(AzureusCore core) { this.core = core; core.getGlobalManager().addListener(this); startedListeningOn = SystemTime.getCurrentTime(); } public void destroyInitiated() { } public void destroyed() { } public void downloadManagerAdded(DownloadManager dm) { if (!dm.getDownloadState().getFlag(DownloadManagerState.FLAG_LOW_NOISE)) { dm.addPeerListener(this, true); } } public void downloadManagerRemoved(DownloadManager dm) { dm.removePeerListener(this); } public void seedingStatusChanged(boolean seedingOnlyMode, boolean potentiallySeedingOnlyMode) { } public void peerAdded(PEPeer peer) { System.out.println("adding peer from ClientStatsView"); peer.addListener(new PEPeerListener() { public void stateChanged(PEPeer peer, int newState) { if (newState == PEPeer.TRANSFERING) { addPeer(peer); } else if (newState == PEPeer.CLOSING || newState == PEPeer.DISCONNECTED) { peer.removeListener(this); } } public void sentBadChunk(PEPeer peer, int pieceNum, int totalBadChunks) { } public void removeAvailability(PEPeer peer, BitFlags peerHavePieces) { } public void addAvailability(PEPeer peer, BitFlags peerHavePieces) { } }); } protected void addPeer(PEPeer peer) { byte[] bloomId; long now = SystemTime.getCurrentTime(); // Bloom Filter is based on the first 8 bytes of peer id + ip address // This captures more duplicates than peer id because most clients // randomize their peer id on restart. IP address, however, changes // less often. byte[] peerId = peer.getId(); InetAddress ip = peer.getAlternativeIPv6(); if (ip == null) { try { ip = InetAddress.getByName(peer.getIp()); } catch (UnknownHostException e) { } } if (ip == null) { bloomId = peerId; } else { byte[] address = ip.getAddress(); bloomId = new byte[8 + address.length]; System.arraycopy(peerId, 0, bloomId, 0, 8); System.arraycopy(address, 0, bloomId, 8, address.length); } synchronized (mapData) { // break on month.. assume user didn't last use this on the same month in a different year calendar.setTimeInMillis(now); int thisMonth = calendar.get(Calendar.MONTH); if (thisMonth != lastAddMonth) { if (lastAddMonth == 0) { lastAddMonth = thisMonth; } else { String s = new SimpleDateFormat("yyyy-MM").format(new Date(lastAdd)); String filename = CONFIG_FILE_ARCHIVE.replace("%1", s); save(filename); lastAddMonth = thisMonth; lastAdd = 0; bloomFilter = BloomFilterFactory.createRotating( BloomFilterFactory.createAddOnly(BLOOMFILTER_SIZE), 2); bloomFilterPeerId = BloomFilterFactory.createRotating( BloomFilterFactory.createAddOnly(BLOOMFILTER_PEERID_SIZE), 2); overall = new ClientStatsOverall(); mapData.clear(); tv.removeAllTableRows(); totalTime = 0; startedListeningOn = 0; } } if (bloomFilter.contains(bloomId) || bloomFilterPeerId.contains(peerId)) { return; } bloomFilter.add(bloomId); bloomFilterPeerId.add(peerId); lastAdd = now; String id = getID(peer); ClientStatsDataSource stat = mapData.get(id); boolean needNew = stat == null; if (needNew) { stat = new ClientStatsDataSource(); stat.overall = overall; mapData.put(id, stat); } overall.count++; stat.client = getID(peer); stat.count++; stat.current++; if (needNew) { tv.addDataSource(stat); } else { TableRowCore row = tv.getRow(stat); if (row != null) { row.invalidate(); } } } } public void peerManagerAdded(PEPeerManager manager) { } public void peerManagerRemoved(PEPeerManager manager) { } public void peerManagerWillBeAdded(PEPeerManager manager) { } public void peerRemoved(PEPeer peer) { synchronized (mapData) { ClientStatsDataSource stat = mapData.get(getID(peer)); if (stat != null) { stat.current--; stat.bytesReceived += peer.getStats().getTotalDataBytesReceived(); stat.bytesSent += peer.getStats().getTotalDataBytesSent(); stat.bytesDiscarded += peer.getStats().getTotalBytesDiscarded(); TableRowCore row = tv.getRow(stat); if (row != null) { row.invalidate(); } } } } private String getID(PEPeer peer) { String s = peer.getClient(); return s.replaceAll(" v?[0-9._]+", ""); } }