blob: a735811a368712b1ed95e61a0c2764cb7cc7b198 [file] [log] [blame]
// Copyright 2014 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 "content/renderer/media/webrtc/media_stream_track_metrics.h"
#include <inttypes.h>
#include <string>
#include "base/md5.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/child/child_thread_impl.h"
#include "content/public/common/service_names.mojom.h"
#include "content/renderer/render_thread_impl.h"
#include "services/service_manager/public/cpp/connector.h"
namespace content {
class MediaStreamTrackMetricsObserver {
public:
MediaStreamTrackMetricsObserver(MediaStreamTrackMetrics::Direction direction,
MediaStreamTrackMetrics::Kind kind,
std::string track_id,
MediaStreamTrackMetrics* owner);
~MediaStreamTrackMetricsObserver();
// Sends begin/end messages for the track if not already reported.
void SendLifetimeMessageForTrack(
MediaStreamTrackMetrics::LifetimeEvent event);
MediaStreamTrackMetrics::Direction direction() {
DCHECK(thread_checker_.CalledOnValidThread());
return direction_;
}
MediaStreamTrackMetrics::Kind kind() {
DCHECK(thread_checker_.CalledOnValidThread());
return kind_;
}
std::string track_id() const {
DCHECK(thread_checker_.CalledOnValidThread());
return track_id_;
}
private:
// False until start/end of lifetime messages have been sent.
bool has_reported_start_;
bool has_reported_end_;
MediaStreamTrackMetrics::Direction direction_;
MediaStreamTrackMetrics::Kind kind_;
std::string track_id_;
// Non-owning.
MediaStreamTrackMetrics* owner_;
base::ThreadChecker thread_checker_;
};
namespace {
// Used with std::find_if.
struct ObserverFinder {
ObserverFinder(MediaStreamTrackMetrics::Direction direction,
MediaStreamTrackMetrics::Kind kind,
const std::string& track_id)
: direction_(direction), kind_(kind), track_id_(track_id) {}
bool operator()(
const std::unique_ptr<MediaStreamTrackMetricsObserver>& observer) {
return direction_ == observer->direction() && kind_ == observer->kind() &&
track_id_ == observer->track_id();
}
MediaStreamTrackMetrics::Direction direction_;
MediaStreamTrackMetrics::Kind kind_;
std::string track_id_;
};
} // namespace
MediaStreamTrackMetricsObserver::MediaStreamTrackMetricsObserver(
MediaStreamTrackMetrics::Direction direction,
MediaStreamTrackMetrics::Kind kind,
std::string track_id,
MediaStreamTrackMetrics* owner)
: has_reported_start_(false),
has_reported_end_(false),
direction_(direction),
kind_(kind),
track_id_(std::move(track_id)),
owner_(owner) {
DCHECK(owner);
}
MediaStreamTrackMetricsObserver::~MediaStreamTrackMetricsObserver() {
DCHECK(thread_checker_.CalledOnValidThread());
SendLifetimeMessageForTrack(
MediaStreamTrackMetrics::LifetimeEvent::kDisconnected);
}
void MediaStreamTrackMetricsObserver::SendLifetimeMessageForTrack(
MediaStreamTrackMetrics::LifetimeEvent event) {
DCHECK(thread_checker_.CalledOnValidThread());
if (event == MediaStreamTrackMetrics::LifetimeEvent::kConnected) {
// Both ICE CONNECTED and COMPLETED can trigger the first
// start-of-life event, so we only report the first.
if (has_reported_start_)
return;
DCHECK(!has_reported_start_ && !has_reported_end_);
has_reported_start_ = true;
} else {
DCHECK(event == MediaStreamTrackMetrics::LifetimeEvent::kDisconnected);
// We only report the first end-of-life event, since there are
// several cases where end-of-life can be reached. We also don't
// report end unless we've reported start.
if (has_reported_end_ || !has_reported_start_)
return;
has_reported_end_ = true;
}
owner_->SendLifetimeMessage(track_id_, kind_, event, direction_);
if (event == MediaStreamTrackMetrics::LifetimeEvent::kDisconnected) {
// After disconnection, we can get reconnected, so we need to
// forget that we've sent lifetime events, while retaining all
// other state.
DCHECK(has_reported_start_ && has_reported_end_);
has_reported_start_ = false;
has_reported_end_ = false;
}
}
MediaStreamTrackMetrics::MediaStreamTrackMetrics()
: ice_state_(webrtc::PeerConnectionInterface::kIceConnectionNew) {}
MediaStreamTrackMetrics::~MediaStreamTrackMetrics() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (const auto& observer : observers_) {
observer->SendLifetimeMessageForTrack(LifetimeEvent::kDisconnected);
}
}
void MediaStreamTrackMetrics::AddTrack(Direction direction,
Kind kind,
const std::string& track_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.push_back(std::make_unique<MediaStreamTrackMetricsObserver>(
direction, kind, std::move(track_id), this));
SendLifeTimeMessageDependingOnIceState(observers_.back().get());
}
void MediaStreamTrackMetrics::RemoveTrack(Direction direction,
Kind kind,
const std::string& track_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = std::find_if(observers_.begin(), observers_.end(),
ObserverFinder(direction, kind, track_id));
if (it == observers_.end()) {
// Since external apps could call removeTrack() with a stream they
// never added, this can happen without it being an error.
return;
}
observers_.erase(it);
}
void MediaStreamTrackMetrics::IceConnectionChange(
webrtc::PeerConnectionInterface::IceConnectionState new_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ice_state_ = new_state;
for (const auto& observer : observers_) {
SendLifeTimeMessageDependingOnIceState(observer.get());
}
}
void MediaStreamTrackMetrics::SendLifeTimeMessageDependingOnIceState(
MediaStreamTrackMetricsObserver* observer) {
// There is a state transition diagram for these states at
// http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCIceConnectionState
switch (ice_state_) {
case webrtc::PeerConnectionInterface::kIceConnectionConnected:
case webrtc::PeerConnectionInterface::kIceConnectionCompleted:
observer->SendLifetimeMessageForTrack(LifetimeEvent::kConnected);
break;
case webrtc::PeerConnectionInterface::kIceConnectionFailed:
// We don't really need to handle FAILED (it is only supposed
// to be preceded by CHECKING so we wouldn't yet have sent a
// lifetime message) but we might as well use belt and
// suspenders and handle it the same as the other "end call"
// states. It will be ignored anyway if the call is not
// already connected.
case webrtc::PeerConnectionInterface::kIceConnectionNew:
// It's a bit weird to count NEW as an end-lifetime event, but
// it's possible to transition directly from a connected state
// (CONNECTED or COMPLETED) to NEW, which can then be followed
// by a new connection. The observer will ignore the end
// lifetime event if it was not preceded by a begin-lifetime
// event.
case webrtc::PeerConnectionInterface::kIceConnectionDisconnected:
case webrtc::PeerConnectionInterface::kIceConnectionClosed:
observer->SendLifetimeMessageForTrack(LifetimeEvent::kDisconnected);
break;
default:
// We ignore the remaining state (CHECKING) as it is never
// involved in a transition from connected to disconnected or
// vice versa.
break;
}
}
void MediaStreamTrackMetrics::SendLifetimeMessage(const std::string& track_id,
Kind kind,
LifetimeEvent event,
Direction direction) {
RenderThreadImpl* render_thread = RenderThreadImpl::current();
// |render_thread| can be NULL in certain cases when running as part
// |of a unit test.
if (render_thread) {
if (event == LifetimeEvent::kConnected) {
GetMediaStreamTrackMetricsHost()->AddTrack(
MakeUniqueId(track_id, direction), kind == Kind::kAudio,
direction == Direction::kReceive);
} else {
DCHECK_EQ(LifetimeEvent::kDisconnected, event);
GetMediaStreamTrackMetricsHost()->RemoveTrack(
MakeUniqueId(track_id, direction));
}
}
}
uint64_t MediaStreamTrackMetrics::MakeUniqueIdImpl(uint64_t pc_id,
const std::string& track_id,
Direction direction) {
// We use a hash over the |track| pointer and the PeerConnection ID,
// plus a boolean flag indicating whether the track is remote (since
// you might conceivably have a remote track added back as a sent
// track) as the unique ID.
//
// We don't need a cryptographically secure hash (which MD5 should
// no longer be considered), just one with virtually zero chance of
// collisions when faced with non-malicious data.
std::string unique_id_string =
base::StringPrintf("%" PRIu64 " %s %d", pc_id, track_id.c_str(),
direction == Direction::kReceive ? 1 : 0);
base::MD5Context ctx;
base::MD5Init(&ctx);
base::MD5Update(&ctx, unique_id_string);
base::MD5Digest digest;
base::MD5Final(&digest, &ctx);
static_assert(sizeof(digest.a) > sizeof(uint64_t), "need a bigger digest");
return *reinterpret_cast<uint64_t*>(digest.a);
}
uint64_t MediaStreamTrackMetrics::MakeUniqueId(const std::string& track_id,
Direction direction) {
return MakeUniqueIdImpl(
reinterpret_cast<uint64_t>(reinterpret_cast<void*>(this)), track_id,
direction);
}
blink::mojom::MediaStreamTrackMetricsHostPtr&
MediaStreamTrackMetrics::GetMediaStreamTrackMetricsHost() {
if (!track_metrics_host_) {
ChildThreadImpl::current()->GetConnector()->BindInterface(
mojom::kBrowserServiceName, &track_metrics_host_);
}
return track_metrics_host_;
}
} // namespace content