blob: c2faeeff97e0d5c1ea7d113f418eebf00c6d324d [file] [log] [blame]
// Copyright 2020 The Chromium 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 "chrome/browser/nearby_sharing/payload_tracker.h"
#include "base/callback.h"
#include "chrome/browser/nearby_sharing/constants.h"
#include "chrome/browser/nearby_sharing/logging/logging.h"
#include "chrome/browser/nearby_sharing/nearby_share_metrics_logger.h"
#include "chrome/browser/nearby_sharing/transfer_metadata_builder.h"
PayloadTracker::PayloadTracker(
const ShareTarget& share_target,
const base::flat_map<int64_t, AttachmentInfo>& attachment_info_map,
base::RepeatingCallback<void(ShareTarget, TransferMetadata)>
update_callback)
: share_target_(share_target),
update_callback_(std::move(update_callback)) {
total_transfer_size_ = 0;
for (const auto& file : share_target.file_attachments) {
auto it = attachment_info_map.find(file.id());
if (it == attachment_info_map.end() || !it->second.payload_id) {
NS_LOG(WARNING)
<< __func__
<< ": Failed to retrieve payload for file attachment id - "
<< file.id();
continue;
}
payload_state_.emplace(*it->second.payload_id, State(file.size()));
++num_file_attachments_;
total_transfer_size_ += file.size();
}
for (const auto& text : share_target.text_attachments) {
auto it = attachment_info_map.find(text.id());
if (it == attachment_info_map.end() || !it->second.payload_id) {
NS_LOG(WARNING)
<< __func__
<< ": Failed to retrieve payload for text attachment id - "
<< text.id();
continue;
}
payload_state_.emplace(*it->second.payload_id, State(text.size()));
++num_text_attachments_;
total_transfer_size_ += text.size();
}
}
PayloadTracker::~PayloadTracker() = default;
void PayloadTracker::OnStatusUpdate(PayloadTransferUpdatePtr update,
base::Optional<Medium> upgraded_medium) {
auto it = payload_state_.find(update->payload_id);
if (it == payload_state_.end())
return;
// For metrics.
if (!first_update_timestamp_.has_value()) {
first_update_timestamp_ = base::TimeTicks::Now();
num_first_update_bytes_ = update->bytes_transferred;
}
if (upgraded_medium.has_value()) {
last_upgraded_medium_ = upgraded_medium;
}
if (it->second.status != update->status) {
it->second.status = update->status;
NS_LOG(VERBOSE) << __func__ << ": Payload id " << update->payload_id
<< " had status change: " << update->status;
}
// The number of bytes transferred should never go down. That said, some
// status updates like cancellation might send a value of 0. In that case, we
// retain the last known value for use in metrics.
if (update->bytes_transferred > it->second.amount_transferred) {
it->second.amount_transferred = update->bytes_transferred;
}
OnTransferUpdate();
}
void PayloadTracker::OnTransferUpdate() {
if (IsComplete()) {
NS_LOG(VERBOSE) << __func__ << ": All payloads are complete.";
EmitFinalMetrics(
location::nearby::connections::mojom::PayloadStatus::kSuccess);
update_callback_.Run(share_target_,
TransferMetadataBuilder()
.set_status(TransferMetadata::Status::kComplete)
.set_progress(100)
.build());
return;
}
if (IsCancelled()) {
NS_LOG(VERBOSE) << __func__ << ": Payloads cancelled.";
EmitFinalMetrics(
location::nearby::connections::mojom::PayloadStatus::kCanceled);
update_callback_.Run(share_target_,
TransferMetadataBuilder()
.set_status(TransferMetadata::Status::kCancelled)
.build());
return;
}
if (HasFailed()) {
NS_LOG(VERBOSE) << __func__ << ": Payloads failed.";
EmitFinalMetrics(
location::nearby::connections::mojom::PayloadStatus::kFailure);
update_callback_.Run(share_target_,
TransferMetadataBuilder()
.set_status(TransferMetadata::Status::kFailed)
.build());
return;
}
double percent = CalculateProgressPercent();
int current_progress = static_cast<int>(percent * 100);
base::Time current_time = base::Time::Now();
if (current_progress == last_update_progress_ ||
(current_time - last_update_timestamp_) < kMinProgressUpdateFrequency) {
return;
}
last_update_progress_ = current_progress;
last_update_timestamp_ = current_time;
update_callback_.Run(share_target_,
TransferMetadataBuilder()
.set_status(TransferMetadata::Status::kInProgress)
.set_progress(percent)
.build());
}
bool PayloadTracker::IsComplete() const {
for (const auto& state : payload_state_) {
if (state.second.status !=
location::nearby::connections::mojom::PayloadStatus::kSuccess) {
return false;
}
}
return true;
}
bool PayloadTracker::IsCancelled() const {
for (const auto& state : payload_state_) {
if (state.second.status ==
location::nearby::connections::mojom::PayloadStatus::kCanceled) {
return true;
}
}
return false;
}
bool PayloadTracker::HasFailed() const {
for (const auto& state : payload_state_) {
if (state.second.status ==
location::nearby::connections::mojom::PayloadStatus::kFailure) {
return true;
}
}
return false;
}
uint64_t PayloadTracker::GetTotalTransferred() const {
uint64_t total_transferred = 0;
for (const auto& state : payload_state_)
total_transferred += state.second.amount_transferred;
return total_transferred;
}
double PayloadTracker::CalculateProgressPercent() const {
if (!total_transfer_size_) {
NS_LOG(WARNING) << __func__ << ": Total attachment size is 0";
return 100.0;
}
return (100.0 * GetTotalTransferred()) / total_transfer_size_;
}
void PayloadTracker::EmitFinalMetrics(
location::nearby::connections::mojom::PayloadStatus status) const {
DCHECK_NE(status,
location::nearby::connections::mojom::PayloadStatus::kInProgress);
RecordNearbySharePayloadFinalStatusMetric(status, last_upgraded_medium_);
RecordNearbySharePayloadMediumMetric(
last_upgraded_medium_, share_target_.type, GetTotalTransferred());
RecordNearbySharePayloadSizeMetric(share_target_.is_incoming,
share_target_.type, last_upgraded_medium_,
status, total_transfer_size_);
RecordNearbySharePayloadNumAttachmentsMetric(num_text_attachments_,
num_file_attachments_);
// Because we only start tracking after receiving the first status update,
// subtract off that first transfer size.
uint64_t transferred_bytes_with_offset =
GetTotalTransferred() - num_first_update_bytes_;
if (first_update_timestamp_ && transferred_bytes_with_offset > 0) {
RecordNearbySharePayloadTransferRateMetric(
share_target_.is_incoming, share_target_.type, last_upgraded_medium_,
status, transferred_bytes_with_offset,
base::TimeTicks::Now() - *first_update_timestamp_);
}
for (const auto& file_attachment : share_target_.file_attachments) {
RecordNearbySharePayloadFileAttachmentTypeMetric(
file_attachment.type(), share_target_.is_incoming, status);
}
for (const auto& text_attachment : share_target_.text_attachments) {
RecordNearbySharePayloadTextAttachmentTypeMetric(
text_attachment.type(), share_target_.is_incoming, status);
}
}