| // Copyright 2020 The Chromium Authors |
| // 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/functional/callback.h" |
| #include "chrome/browser/nearby_sharing/common/nearby_share_features.h" |
| #include "chrome/browser/nearby_sharing/constants.h" |
| #include "chrome/browser/nearby_sharing/nearby_share_metrics.h" |
| #include "chrome/browser/nearby_sharing/transfer_metadata.h" |
| #include "chrome/browser/nearby_sharing/transfer_metadata_builder.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "components/cross_device/logging/logging.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) { |
| CD_LOG(WARNING, Feature::NS) |
| << __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) { |
| CD_LOG(WARNING, Feature::NS) |
| << __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(); |
| } |
| |
| for (const auto& wifi_credentials : |
| share_target.wifi_credentials_attachments) { |
| auto it = attachment_info_map.find(wifi_credentials.id()); |
| if (it == attachment_info_map.end() || !it->second.payload_id) { |
| CD_LOG(WARNING, Feature::NS) |
| << __func__ << ": Failed to retrieve payload for Wi-Fi Credentials " |
| << "attachment id - " << wifi_credentials.id(); |
| continue; |
| } |
| |
| payload_state_.emplace(*it->second.payload_id, State(0)); |
| ++num_wifi_credentials_attachments_; |
| total_transfer_size_ += wifi_credentials.size(); |
| } |
| } |
| |
| PayloadTracker::~PayloadTracker() = default; |
| |
| void PayloadTracker::OnStatusUpdate(PayloadTransferUpdatePtr update, |
| std::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; |
| |
| CD_LOG(VERBOSE, Feature::NS) |
| << __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() { |
| const double percent = CalculateProgressPercent(); |
| if (IsComplete()) { |
| const bool is_transfer_complete = |
| GetTotalTransferred() >= total_transfer_size_; |
| if (is_transfer_complete) { |
| CD_LOG(VERBOSE, Feature::NS) |
| << __func__ << ": All payloads are complete."; |
| EmitFinalMetrics(nearby::connections::mojom::PayloadStatus::kSuccess); |
| update_callback_.Run(share_target_, |
| TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kComplete) |
| .set_progress(100) |
| .build()); |
| return; |
| } |
| |
| CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Payloads incomplete."; |
| EmitFinalMetrics(nearby::connections::mojom::PayloadStatus::kFailure); |
| update_callback_.Run( |
| share_target_, |
| TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kIncompletePayloads) |
| .set_progress(percent) |
| .build()); |
| |
| return; |
| } |
| |
| if (IsCancelled()) { |
| CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Payloads cancelled."; |
| EmitFinalMetrics(nearby::connections::mojom::PayloadStatus::kCanceled); |
| update_callback_.Run(share_target_, |
| TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kCancelled) |
| .build()); |
| return; |
| } |
| |
| if (HasFailed()) { |
| CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Payloads failed."; |
| EmitFinalMetrics(nearby::connections::mojom::PayloadStatus::kFailure); |
| update_callback_.Run(share_target_, |
| TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kFailed) |
| .build()); |
| return; |
| } |
| |
| const 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 != |
| nearby::connections::mojom::PayloadStatus::kSuccess) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PayloadTracker::IsCancelled() const { |
| for (const auto& state : payload_state_) { |
| if (state.second.status == |
| nearby::connections::mojom::PayloadStatus::kCanceled) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool PayloadTracker::HasFailed() const { |
| for (const auto& state : payload_state_) { |
| if (state.second.status == |
| 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_) { |
| CD_LOG(WARNING, Feature::NS) << __func__ << ": Total attachment size is 0"; |
| return 100.0; |
| } |
| |
| return (100.0 * GetTotalTransferred()) / total_transfer_size_; |
| } |
| |
| void PayloadTracker::EmitFinalMetrics( |
| nearby::connections::mojom::PayloadStatus status) const { |
| DCHECK_NE(status, 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_, |
| num_wifi_credentials_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, |
| share_target_.is_known, share_target_.for_self_share, status); |
| } |
| |
| for (const auto& text_attachment : share_target_.text_attachments) { |
| RecordNearbySharePayloadTextAttachmentTypeMetric( |
| text_attachment.type(), share_target_.is_incoming, |
| share_target_.is_known, share_target_.for_self_share, status); |
| } |
| |
| for (size_t i = 0; i < share_target_.wifi_credentials_attachments.size(); |
| ++i) { |
| RecordNearbySharePayloadWifiCredentialsAttachmentTypeMetric( |
| share_target_.is_incoming, share_target_.is_known, |
| share_target_.for_self_share, status); |
| } |
| } |