OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/media/media_internals.h" | 5 #include "content/browser/media/media_internals.h" |
6 | 6 |
7 #include "base/metrics/histogram.h" | 7 #include "base/metrics/histogram.h" |
8 #include "base/strings/string16.h" | 8 #include "base/strings/string16.h" |
9 #include "base/strings/string_number_conversions.h" | 9 #include "base/strings/string_number_conversions.h" |
10 #include "base/strings/stringprintf.h" | 10 #include "base/strings/stringprintf.h" |
11 #include "content/public/browser/browser_thread.h" | 11 #include "content/public/browser/browser_thread.h" |
12 #include "content/public/browser/notification_observer.h" | |
13 #include "content/public/browser/notification_registrar.h" | |
14 #include "content/public/browser/notification_service.h" | 12 #include "content/public/browser/notification_service.h" |
15 #include "content/public/browser/notification_types.h" | 13 #include "content/public/browser/notification_types.h" |
16 #include "content/public/browser/render_frame_host.h" | 14 #include "content/public/browser/render_frame_host.h" |
17 #include "content/public/browser/render_process_host.h" | 15 #include "content/public/browser/render_process_host.h" |
18 #include "content/public/browser/web_contents.h" | 16 #include "content/public/browser/web_contents.h" |
19 #include "content/public/browser/web_ui.h" | 17 #include "content/public/browser/web_ui.h" |
20 #include "media/audio/audio_parameters.h" | 18 #include "media/audio/audio_parameters.h" |
21 #include "media/base/media_log_event.h" | 19 #include "media/base/media_log_event.h" |
22 #include "media/filters/decrypting_video_decoder.h" | 20 #include "media/filters/decrypting_video_decoder.h" |
23 #include "media/filters/gpu_video_decoder.h" | 21 #include "media/filters/gpu_video_decoder.h" |
(...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
242 kAudioLogUpdateFunction, &dict); | 240 kAudioLogUpdateFunction, &dict); |
243 } | 241 } |
244 | 242 |
245 void AudioLogImpl::StoreComponentMetadata(int component_id, | 243 void AudioLogImpl::StoreComponentMetadata(int component_id, |
246 base::DictionaryValue* dict) { | 244 base::DictionaryValue* dict) { |
247 dict->SetInteger("owner_id", owner_id_); | 245 dict->SetInteger("owner_id", owner_id_); |
248 dict->SetInteger("component_id", component_id); | 246 dict->SetInteger("component_id", component_id); |
249 dict->SetInteger("component_type", component_); | 247 dict->SetInteger("component_type", component_); |
250 } | 248 } |
251 | 249 |
252 class MediaInternals::MediaInternalsUMAHandler : public NotificationObserver { | 250 class MediaInternals::MediaInternalsUMAHandler { |
253 public: | 251 public: |
254 MediaInternalsUMAHandler(); | 252 MediaInternalsUMAHandler(); |
255 | 253 |
256 // NotificationObserver implementation. | 254 // Called when a render process is terminated. Reports the pipeline status to |
257 void Observe(int type, | 255 // UMA for every player associated with the renderer process and then deletes |
258 const NotificationSource& source, | 256 // the player state. |
259 const NotificationDetails& details) override; | 257 void OnProcessTerminated(int render_process_id); |
260 | |
261 // Reports the pipeline status to UMA for every player | |
262 // associated with the renderer process and then deletes the player state. | |
263 void LogAndClearPlayersInRenderer(int render_process_id); | |
264 | 258 |
265 // Helper function to save the event payload to RendererPlayerMap. | 259 // Helper function to save the event payload to RendererPlayerMap. |
266 void SavePlayerState(const media::MediaLogEvent& event, | 260 void SavePlayerState(int render_process_id, |
267 int render_process_id); | 261 const media::MediaLogEvent& event); |
268 | 262 |
269 private: | 263 private: |
270 struct PipelineInfo { | 264 struct PipelineInfo { |
271 media::PipelineStatus last_pipeline_status; | 265 media::PipelineStatus last_pipeline_status; |
272 bool has_audio; | 266 bool has_audio; |
273 bool has_video; | 267 bool has_video; |
274 bool video_dds; | 268 bool video_dds; |
275 bool video_decoder_changed; | 269 bool video_decoder_changed; |
276 std::string audio_codec_name; | 270 std::string audio_codec_name; |
277 std::string video_codec_name; | 271 std::string video_codec_name; |
(...skipping 14 matching lines...) Expand all Loading... |
292 | 286 |
293 // Key is playerid | 287 // Key is playerid |
294 typedef std::map<int, PipelineInfo> PlayerInfoMap; | 288 typedef std::map<int, PipelineInfo> PlayerInfoMap; |
295 | 289 |
296 // Key is renderer id | 290 // Key is renderer id |
297 typedef std::map<int, PlayerInfoMap> RendererPlayerMap; | 291 typedef std::map<int, PlayerInfoMap> RendererPlayerMap; |
298 | 292 |
299 // Stores player information per renderer | 293 // Stores player information per renderer |
300 RendererPlayerMap renderer_info_; | 294 RendererPlayerMap renderer_info_; |
301 | 295 |
302 NotificationRegistrar registrar_; | |
303 | |
304 DISALLOW_COPY_AND_ASSIGN(MediaInternalsUMAHandler); | 296 DISALLOW_COPY_AND_ASSIGN(MediaInternalsUMAHandler); |
305 }; | 297 }; |
306 | 298 |
307 MediaInternals::MediaInternalsUMAHandler::MediaInternalsUMAHandler() { | 299 MediaInternals::MediaInternalsUMAHandler::MediaInternalsUMAHandler() { |
308 registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED, | |
309 NotificationService::AllBrowserContextsAndSources()); | |
310 } | |
311 | |
312 void MediaInternals::MediaInternalsUMAHandler::Observe( | |
313 int type, | |
314 const NotificationSource& source, | |
315 const NotificationDetails& details) { | |
316 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
317 DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED); | |
318 RenderProcessHost* process = Source<RenderProcessHost>(source).ptr(); | |
319 | |
320 // Post the task to the IO thread to avoid race in updating renderer_info_ map | |
321 // by both SavePlayerState & LogAndClearPlayersInRenderer from different | |
322 // threads. | |
323 // Using base::Unretained() on MediaInternalsUMAHandler is safe since | |
324 // it is owned by MediaInternals and share the same lifetime | |
325 BrowserThread::PostTask( | |
326 BrowserThread::IO, FROM_HERE, | |
327 base::Bind(&MediaInternalsUMAHandler::LogAndClearPlayersInRenderer, | |
328 base::Unretained(this), process->GetID())); | |
329 } | 300 } |
330 | 301 |
331 void MediaInternals::MediaInternalsUMAHandler::SavePlayerState( | 302 void MediaInternals::MediaInternalsUMAHandler::SavePlayerState( |
332 const media::MediaLogEvent& event, | 303 int render_process_id, |
333 int render_process_id) { | 304 const media::MediaLogEvent& event) { |
334 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 305 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
335 PlayerInfoMap& player_info = renderer_info_[render_process_id]; | 306 PlayerInfoMap& player_info = renderer_info_[render_process_id]; |
336 switch (event.type) { | 307 switch (event.type) { |
337 case media::MediaLogEvent::PIPELINE_ERROR: { | 308 case media::MediaLogEvent::PIPELINE_ERROR: { |
338 int status; | 309 int status; |
339 event.params.GetInteger("pipeline_error", &status); | 310 event.params.GetInteger("pipeline_error", &status); |
340 player_info[event.id].last_pipeline_status = | 311 player_info[event.id].last_pipeline_status = |
341 static_cast<media::PipelineStatus>(status); | 312 static_cast<media::PipelineStatus>(status); |
342 break; | 313 break; |
343 } | 314 } |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
432 media::PIPELINE_STATUS_MAX + 1); | 403 media::PIPELINE_STATUS_MAX + 1); |
433 } | 404 } |
434 // Report whether video decoder fallback happened, but only if a video decoder | 405 // Report whether video decoder fallback happened, but only if a video decoder |
435 // was reported. | 406 // was reported. |
436 if (!player_info.video_decoder.empty()) { | 407 if (!player_info.video_decoder.empty()) { |
437 UMA_HISTOGRAM_BOOLEAN("Media.VideoDecoderFallback", | 408 UMA_HISTOGRAM_BOOLEAN("Media.VideoDecoderFallback", |
438 player_info.video_decoder_changed); | 409 player_info.video_decoder_changed); |
439 } | 410 } |
440 } | 411 } |
441 | 412 |
442 void MediaInternals::MediaInternalsUMAHandler::LogAndClearPlayersInRenderer( | 413 void MediaInternals::MediaInternalsUMAHandler::OnProcessTerminated( |
443 int render_process_id) { | 414 int render_process_id) { |
444 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 415 // Ensures to run on the IO thread to avoid race in |updating renderer_info_| |
| 416 // by both SavePlayerState and OnProcessTerminated. |
| 417 // Using base::Unretained() on MediaInternalsUMAHandler is safe since |
| 418 // it is owned by MediaInternals and shares the same lifetime. |
| 419 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| 420 BrowserThread::PostTask( |
| 421 BrowserThread::IO, FROM_HERE, |
| 422 base::Bind(&MediaInternalsUMAHandler::OnProcessTerminated, |
| 423 base::Unretained(this), render_process_id)); |
| 424 return; |
| 425 } |
| 426 |
445 auto players_it = renderer_info_.find(render_process_id); | 427 auto players_it = renderer_info_.find(render_process_id); |
446 if (players_it == renderer_info_.end()) | 428 if (players_it == renderer_info_.end()) |
447 return; | 429 return; |
448 auto it = players_it->second.begin(); | 430 auto it = players_it->second.begin(); |
449 while (it != players_it->second.end()) { | 431 while (it != players_it->second.end()) { |
450 ReportUMAForPipelineStatus(it->second); | 432 ReportUMAForPipelineStatus(it->second); |
451 players_it->second.erase(it++); | 433 players_it->second.erase(it++); |
452 } | 434 } |
| 435 renderer_info_.erase(players_it); |
453 } | 436 } |
454 | 437 |
455 MediaInternals* MediaInternals::GetInstance() { | 438 MediaInternals* MediaInternals::GetInstance() { |
456 return g_media_internals.Pointer(); | 439 return g_media_internals.Pointer(); |
457 } | 440 } |
458 | 441 |
459 MediaInternals::MediaInternals() | 442 MediaInternals::MediaInternals() |
460 : can_update_(false), | 443 : can_update_(false), |
461 owner_ids_(), | 444 owner_ids_(), |
462 uma_handler_(new MediaInternalsUMAHandler()) { | 445 uma_handler_(new MediaInternalsUMAHandler()) { |
| 446 registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED, |
| 447 NotificationService::AllBrowserContextsAndSources()); |
463 } | 448 } |
464 | 449 |
465 MediaInternals::~MediaInternals() {} | 450 MediaInternals::~MediaInternals() {} |
466 | 451 |
| 452 void MediaInternals::Observe(int type, |
| 453 const NotificationSource& source, |
| 454 const NotificationDetails& details) { |
| 455 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 456 DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED); |
| 457 RenderProcessHost* process = Source<RenderProcessHost>(source).ptr(); |
| 458 |
| 459 uma_handler_->OnProcessTerminated(process->GetID()); |
| 460 |
| 461 base::AutoLock auto_lock(lock_); |
| 462 pending_events_map_.erase(process->GetID()); |
| 463 } |
| 464 |
| 465 // Converts the |event| to a |update|. Returns whether the conversion succeeded. |
| 466 static bool ConvertEventToUpdate(int render_process_id, |
| 467 const media::MediaLogEvent& event, |
| 468 base::string16* update) { |
| 469 DCHECK(update); |
| 470 |
| 471 base::DictionaryValue dict; |
| 472 dict.SetInteger("renderer", render_process_id); |
| 473 dict.SetInteger("player", event.id); |
| 474 dict.SetString("type", media::MediaLog::EventTypeToString(event.type)); |
| 475 |
| 476 // TODO(dalecurtis): This is technically not correct. TimeTicks "can't" be |
| 477 // converted to to a human readable time format. See base/time/time.h. |
| 478 const double ticks = event.time.ToInternalValue(); |
| 479 const double ticks_millis = ticks / base::Time::kMicrosecondsPerMillisecond; |
| 480 dict.SetDouble("ticksMillis", ticks_millis); |
| 481 |
| 482 // Convert PipelineStatus to human readable string |
| 483 if (event.type == media::MediaLogEvent::PIPELINE_ERROR) { |
| 484 int status; |
| 485 if (!event.params.GetInteger("pipeline_error", &status) || |
| 486 status < static_cast<int>(media::PIPELINE_OK) || |
| 487 status > static_cast<int>(media::PIPELINE_STATUS_MAX)) { |
| 488 return false; |
| 489 } |
| 490 media::PipelineStatus error = static_cast<media::PipelineStatus>(status); |
| 491 dict.SetString("params.pipeline_error", |
| 492 media::MediaLog::PipelineStatusToString(error)); |
| 493 } else { |
| 494 dict.Set("params", event.params.DeepCopy()); |
| 495 } |
| 496 |
| 497 *update = SerializeUpdate("media.onMediaEvent", &dict); |
| 498 return true; |
| 499 } |
| 500 |
467 void MediaInternals::OnMediaEvents( | 501 void MediaInternals::OnMediaEvents( |
468 int render_process_id, const std::vector<media::MediaLogEvent>& events) { | 502 int render_process_id, const std::vector<media::MediaLogEvent>& events) { |
469 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 503 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
470 // Notify observers that |event| has occurred. | 504 // Notify observers that |event| has occurred. |
471 for (auto event = events.begin(); event != events.end(); ++event) { | 505 for (const auto& event : events) { |
472 base::DictionaryValue dict; | 506 if (CanUpdate()) { |
473 dict.SetInteger("renderer", render_process_id); | 507 base::string16 update; |
474 dict.SetInteger("player", event->id); | 508 if (ConvertEventToUpdate(render_process_id, event, &update)) |
475 dict.SetString("type", media::MediaLog::EventTypeToString(event->type)); | 509 SendUpdate(update); |
476 | |
477 // TODO(dalecurtis): This is technically not correct. TimeTicks "can't" be | |
478 // converted to to a human readable time format. See base/time/time.h. | |
479 const double ticks = event->time.ToInternalValue(); | |
480 const double ticks_millis = ticks / base::Time::kMicrosecondsPerMillisecond; | |
481 dict.SetDouble("ticksMillis", ticks_millis); | |
482 | |
483 // Convert PipelineStatus to human readable string | |
484 if (event->type == media::MediaLogEvent::PIPELINE_ERROR) { | |
485 int status; | |
486 if (!event->params.GetInteger("pipeline_error", &status) || | |
487 status < static_cast<int>(media::PIPELINE_OK) || | |
488 status > static_cast<int>(media::PIPELINE_STATUS_MAX)) { | |
489 continue; | |
490 } | |
491 media::PipelineStatus error = static_cast<media::PipelineStatus>(status); | |
492 dict.SetString("params.pipeline_error", | |
493 media::MediaLog::PipelineStatusToString(error)); | |
494 } else { | |
495 dict.Set("params", event->params.DeepCopy()); | |
496 } | 510 } |
497 | 511 |
498 if (CanUpdate()) | 512 SaveEvent(render_process_id, event); |
499 SendUpdate(SerializeUpdate("media.onMediaEvent", &dict)); | 513 uma_handler_->SavePlayerState(render_process_id, event); |
500 uma_handler_->SavePlayerState(*event, render_process_id); | |
501 } | 514 } |
502 } | 515 } |
503 | 516 |
504 void MediaInternals::AddUpdateCallback(const UpdateCallback& callback) { | 517 void MediaInternals::AddUpdateCallback(const UpdateCallback& callback) { |
505 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 518 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
506 update_callbacks_.push_back(callback); | 519 update_callbacks_.push_back(callback); |
507 | 520 |
508 base::AutoLock auto_lock(lock_); | 521 base::AutoLock auto_lock(lock_); |
509 can_update_ = true; | 522 can_update_ = true; |
510 } | 523 } |
511 | 524 |
512 void MediaInternals::RemoveUpdateCallback(const UpdateCallback& callback) { | 525 void MediaInternals::RemoveUpdateCallback(const UpdateCallback& callback) { |
513 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 526 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
514 for (size_t i = 0; i < update_callbacks_.size(); ++i) { | 527 for (size_t i = 0; i < update_callbacks_.size(); ++i) { |
515 if (update_callbacks_[i].Equals(callback)) { | 528 if (update_callbacks_[i].Equals(callback)) { |
516 update_callbacks_.erase(update_callbacks_.begin() + i); | 529 update_callbacks_.erase(update_callbacks_.begin() + i); |
517 break; | 530 break; |
518 } | 531 } |
519 } | 532 } |
520 | 533 |
521 base::AutoLock auto_lock(lock_); | 534 base::AutoLock auto_lock(lock_); |
522 can_update_ = !update_callbacks_.empty(); | 535 can_update_ = !update_callbacks_.empty(); |
523 } | 536 } |
524 | 537 |
525 bool MediaInternals::CanUpdate() { | 538 bool MediaInternals::CanUpdate() { |
526 base::AutoLock auto_lock(lock_); | 539 base::AutoLock auto_lock(lock_); |
527 return can_update_; | 540 return can_update_; |
528 } | 541 } |
529 | 542 |
| 543 void MediaInternals::SendHistoricalMediaEvents() { |
| 544 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 545 base::AutoLock auto_lock(lock_); |
| 546 for (const auto& pending_events : pending_events_map_) { |
| 547 for (const auto& event : pending_events.second) { |
| 548 base::string16 update; |
| 549 if (ConvertEventToUpdate(pending_events.first, event, &update)) |
| 550 SendUpdate(update); |
| 551 } |
| 552 } |
| 553 // Do not clear the map/list here so that refreshing the UI or opening a |
| 554 // second UI still works nicely! |
| 555 } |
| 556 |
530 void MediaInternals::SendAudioStreamData() { | 557 void MediaInternals::SendAudioStreamData() { |
531 base::string16 audio_stream_update; | 558 base::string16 audio_stream_update; |
532 { | 559 { |
533 base::AutoLock auto_lock(lock_); | 560 base::AutoLock auto_lock(lock_); |
534 audio_stream_update = SerializeUpdate( | 561 audio_stream_update = SerializeUpdate( |
535 "media.onReceiveAudioStreamData", &audio_streams_cached_data_); | 562 "media.onReceiveAudioStreamData", &audio_streams_cached_data_); |
536 } | 563 } |
537 SendUpdate(audio_stream_update); | 564 SendUpdate(audio_stream_update); |
538 } | 565 } |
539 | 566 |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
594 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | 621 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
595 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( | 622 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( |
596 &MediaInternals::SendUpdate, base::Unretained(this), update)); | 623 &MediaInternals::SendUpdate, base::Unretained(this), update)); |
597 return; | 624 return; |
598 } | 625 } |
599 | 626 |
600 for (size_t i = 0; i < update_callbacks_.size(); i++) | 627 for (size_t i = 0; i < update_callbacks_.size(); i++) |
601 update_callbacks_[i].Run(update); | 628 update_callbacks_[i].Run(update); |
602 } | 629 } |
603 | 630 |
| 631 void MediaInternals::SaveEvent(int process_id, |
| 632 const media::MediaLogEvent& event) { |
| 633 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 634 |
| 635 // Max number of saved updates allowed for one process. |
| 636 const size_t kMaxNumEvents = 128; |
| 637 |
| 638 // Do not save instantaneous events that happen frequently and have little |
| 639 // value in the future. |
| 640 if (event.type == media::MediaLogEvent::NETWORK_ACTIVITY_SET || |
| 641 event.type == media::MediaLogEvent::BUFFERED_EXTENTS_CHANGED) { |
| 642 return; |
| 643 } |
| 644 |
| 645 base::AutoLock auto_lock(lock_); |
| 646 auto& pending_events = pending_events_map_[process_id]; |
| 647 // TODO(xhwang): Notify user that some old logs could have been truncated. |
| 648 // See http://crbug.com/498520 |
| 649 if (pending_events.size() >= kMaxNumEvents) |
| 650 pending_events.pop_front(); |
| 651 pending_events.push_back(event); |
| 652 } |
| 653 |
604 void MediaInternals::SendAudioLogUpdate(AudioLogUpdateType type, | 654 void MediaInternals::SendAudioLogUpdate(AudioLogUpdateType type, |
605 const std::string& cache_key, | 655 const std::string& cache_key, |
606 const std::string& function, | 656 const std::string& function, |
607 const base::DictionaryValue* value) { | 657 const base::DictionaryValue* value) { |
608 if (!CanUpdate()) | 658 if (!CanUpdate()) |
609 return; | 659 return; |
610 | 660 |
611 { | 661 { |
612 base::AutoLock auto_lock(lock_); | 662 base::AutoLock auto_lock(lock_); |
613 const bool has_entry = audio_streams_cached_data_.HasKey(cache_key); | 663 const bool has_entry = audio_streams_cached_data_.HasKey(cache_key); |
(...skipping 10 matching lines...) Expand all Loading... |
624 CHECK( | 674 CHECK( |
625 audio_streams_cached_data_.GetDictionary(cache_key, &existing_dict)); | 675 audio_streams_cached_data_.GetDictionary(cache_key, &existing_dict)); |
626 existing_dict->MergeDictionary(value); | 676 existing_dict->MergeDictionary(value); |
627 } | 677 } |
628 } | 678 } |
629 | 679 |
630 SendUpdate(SerializeUpdate(function, value)); | 680 SendUpdate(SerializeUpdate(function, value)); |
631 } | 681 } |
632 | 682 |
633 } // namespace content | 683 } // namespace content |
OLD | NEW |