blob: f4ccc90452989e9e9beb223f862b985e9ffd3645 [file] [log] [blame]
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "p2p/client/peer_selector.h"
#include "p2p/client/peer.h"
#include "p2p/common/clock_interface.h"
#include "p2p/common/constants.h"
#include <algorithm>
#include <map>
#include <vector>
#include <base/bind.h>
#include <base/command_line.h>
#include <base/logging.h>
#include <base/rand_util.h>
using std::map;
using std::string;
using std::vector;
namespace p2p {
namespace client {
PeerSelector::PeerSelector(ServiceFinder* finder,
p2p::common::ClockInterface* clock)
: finder_(finder),
clock_(clock),
lookup_result_(kNumLookupResults),
candidate_files_count_(-1),
victim_connections_(-1),
num_total_peers_(-1),
url_waiting_time_sec_(-1),
must_exit_now_(false) {
}
// Type used for std::sort()
struct SortPeerBySize {
explicit SortPeerBySize(const std::string& id) : id_(id) {}
bool operator() (const Peer *a, const Peer *b) {
map<string, size_t>::const_iterator iter_a = a->files.find(id_);
map<string, size_t>::const_iterator iter_b = b->files.find(id_);
if (iter_a == a->files.end())
return false;
// Put all the peers without the id_ file at the end of the ordering.
if (iter_b == b->files.end())
return true;
return iter_a->second > iter_b->second;
}
string id_;
};
string PeerSelector::PickUrlForId(const string& id, size_t minimum_size) {
vector<const Peer*> peers = finder_->GetPeersForFile(id);
// Set an invalid victim_connections_ value in order to catch logic errors
// during test.
victim_connections_ = -1;
// Compute the candidate_files_count_ for metrics purposes.
candidate_files_count_ = 0;
for (auto const& peer : peers) {
map<string, size_t>::const_iterator file_size_it = peer->files.find(id);
if (file_size_it != peer->files.end() && file_size_it->second > 0)
candidate_files_count_++;
}
if (!candidate_files_count_)
return "";
// Sort according to size (largest file size first)
std::sort(peers.begin(), peers.end(), SortPeerBySize(id));
// Don't consider peers with file size lower than minimum_size.
int big_enough_files = 0;
for (auto const& peer : peers) {
map<string, size_t>::const_iterator file_size_it = peer->files.find(id);
if (file_size_it != peer->files.end() &&
file_size_it->second >= minimum_size)
big_enough_files++;
}
peers.resize(big_enough_files);
// Return "" if no peer has a big enough file.
if (!big_enough_files)
return "";
// If we have any files left, pick randomly from the top 33%
int victim_number = 0;
int num_possible_victims = peers.size()/3 - 1;
if (num_possible_victims > 1)
victim_number = base::RandInt(0, num_possible_victims - 1);
const Peer* victim = peers[victim_number];
// Record the number of current connection the victim has.
victim_connections_ = victim->num_connections;
string address = victim->address;
if (victim->is_ipv6)
address = "[" + address + "]";
return string("http://") + address + ":" + std::to_string(victim->port) +
"/" + id;
}
string PeerSelector::GetUrlAndWait(const string& id, size_t minimum_size) {
LOG(INFO) << "Requesting URL in the LAN for ID " << id
<< " (minimum_size=" << minimum_size << ")";
// Set the current state to an invalid condition in order to detect logic
// errors during test.
lookup_result_ = kNumLookupResults;
base::Time init_time = clock_->GetMonotonicTime();
string url;
int num_retries = 0;
do {
if (!finder_->Lookup()) {
lookup_result_ = kFiltered;
break;
}
num_total_peers_ = finder_->NumTotalPeers();
// Check if a signal was received during the lookup.
if (must_exit_now_)
break;
url = PickUrlForId(id, minimum_size);
// If we didn't find a peer, fail.
if (url.size() == 0) {
LOG(INFO) << "Returning error - no peer for the given ID.";
lookup_result_ = num_retries ? kVanished : kNotFound;
break;
}
// Only return the peer if the number of connections in the LAN
// is below the threshold.
int num_total_conn = finder_->NumTotalConnections();
if (num_total_conn < constants::kMaxSimultaneousDownloads) {
LOG(INFO) << "Returning URL " << url << " after " << num_retries
<< " retries.";
lookup_result_ = kFound;
break;
}
LOG(INFO) << "Found peer for the given ID but there are already "
<< num_total_conn << " download(s) in the LAN which exceeds "
<< "the threshold of "
<< constants::kMaxSimultaneousDownloads << " download(s). "
<< "Sleeping "
<< constants::kMaxSimultaneousDownloadsPollTimeSeconds
<< " seconds until retrying.";
clock_->Sleep(base::TimeDelta::FromSeconds(
constants::kMaxSimultaneousDownloadsPollTimeSeconds));
// Now that we've slept for a while, the URL may not be valid
// anymore, so we do the lookup again.
num_retries++;
} while (!must_exit_now_);
if (must_exit_now_) {
LOG(INFO) << "Abort was requested.";
lookup_result_ = kCanceled;
url = "";
}
url_waiting_time_sec_ = (clock_->GetMonotonicTime() - init_time).InSeconds();
return url;
}
void PeerSelector::Abort() {
// Allow several calls to this function.
if (must_exit_now_)
return;
must_exit_now_ = true;
// Signal the termination on the ServiceFinder because we could be blocked
// in Lookup().
finder_->Abort();
}
string PeerSelector::ToString(LookupResult lookup_result) {
switch (lookup_result) {
case kFound: return "Found";
case kNotFound: return "NotFound";
case kVanished: return "Vanished";
case kCanceled: return "Canceled";
case kFiltered: return "Filtered";
// Don't add a default case to let the compiler warn about newly added
// lookup results which should be added here.
case kNumLookupResults: return "Unknown";
}
return "Unknown";
}
bool PeerSelector::ReportMetrics(MetricsLibraryInterface* metrics_lib) {
string metric;
int value;
if (lookup_result_ == kNumLookupResults) {
LOG(ERROR) << "Invalid LookupResult from the previous GetUrlAndWait() "
<< "call. Was it ever called?";
return false;
}
// Report the last lookup_result.
metric = "P2P.Client.LookupResult";
metrics_lib->SendEnumToUMA(metric, lookup_result_, kNumLookupResults);
LOG(INFO) << "Uploading " << ToString(lookup_result_)
<< " for metric " << metric;
if (lookup_result_ != kFiltered && lookup_result_ != kCanceled) {
// Report the number of peers implementing p2p file sharing on the network.
// This metric is only reported if the mDNS service is not filtered.
metric = "P2P.Client.NumPeers";
value = num_total_peers_;
LOG(INFO) << "Uploading " << value << " (count) for metric " << metric;
metrics_lib->SendToUMA(metric, value, 1 /* min */, 1000 /* max */, 100);
}
if (lookup_result_ == kFound) {
// Report
metric = "P2P.Client.Found.WaitingTimeSeconds";
value = url_waiting_time_sec_;
LOG(INFO) << "Uploading " << value << " (count) for metric " << metric;
metrics_lib->SendToUMA(metric, value, 0 /* min */, 86400 /* max */, 100);
metric = "P2P.Client.Found.ConnectionCount";
value = victim_connections_;
LOG(INFO) << "Uploading " << value << " (count) for metric " << metric;
metrics_lib->SendToUMA(metric, value, 0 /* min */, 50 /* max */, 50);
metric = "P2P.Client.Found.CandidateCount";
value = candidate_files_count_;
LOG(INFO) << "Uploading " << value << " (count) for metric " << metric;
metrics_lib->SendToUMA(metric, value, 1 /* min */, 50 /* max */, 50);
}
if (lookup_result_ == kVanished) {
// Report the wall-clock time spent waiting for a resource that vanished.
// Reported only if a URL for the requested resource vanished while
// waiting for it.
metric = "P2P.Client.Vanished.WaitingTimeSeconds";
value = url_waiting_time_sec_;
LOG(INFO) << "Uploading " << value << " (count) for metric " << metric;
metrics_lib->SendToUMA(metric, value, 0 /* min */, 86400 /* max */, 100);
}
if (lookup_result_ == kCanceled) {
// Report the wall-clock time spent until a lookup was cancelled.
metric = "P2P.Client.Canceled.WaitingTimeSeconds";
value = url_waiting_time_sec_;
LOG(INFO) << "Uploading " << value << " (count) for metric " << metric;
metrics_lib->SendToUMA(metric, value, 0 /* min */, 86400 /* max */, 100);
}
return true;
}
} // namespace client
} // namespace p2p