Added metrics history for WV CDM for Android.

[ Merge of http://go/wvgerrit/171271 ]

There is a need to maintain a short history of metrics from CDMs which
have been deleted.  This CL adds this ability to the Android version
of the WV CDM.  The history cannot yet be maintained for long, as the
WV CDM instance is destroyed if unused.

Further changes are required to the plugin to maintain the history
beyond the life-cycle of the CDM instance, and to properly format
its output.

Bug: 239462891
Bug: 270166158
Test: adb shell dumpsys android.hardware.drm.IDrmFactory/widevine -m
Test: atest GtsMediaTestCases
Change-Id: I81c0996602722a9795fc3951030d20bb39b5816b
This commit is contained in:
Alex Dale
2023-04-18 20:15:16 -07:00
parent 5ce29c42da
commit c42627a23e
7 changed files with 259 additions and 80 deletions

View File

@@ -5,12 +5,14 @@
#ifndef CDM_BASE_WV_CONTENT_DECRYPTION_MODULE_H_
#define CDM_BASE_WV_CONTENT_DECRYPTION_MODULE_H_
#include <utils/RefBase.h>
#include <time.h>
#include <map>
#include <memory>
#include <mutex>
#include <utils/RefBase.h>
#include "cdm_identifier.h"
#include "disallow_copy_and_assign.h"
#include "file_store.h"
@@ -24,6 +26,32 @@ class CdmClientPropertySet;
class CdmEngine;
class WvCdmEventListener;
class WvMetricsSnapshot {
public:
WvMetricsSnapshot(const CdmIdentifier& identifier,
drm_metrics::WvCdmMetrics&& metrics, ::time_t ts);
// Acts as a constructor, but uses the current time.
static WvMetricsSnapshot MakeSnapshot(const CdmIdentifier& identifier,
drm_metrics::WvCdmMetrics&& metrics);
WvMetricsSnapshot() = default;
WvMetricsSnapshot(const WvMetricsSnapshot&) = default;
WvMetricsSnapshot(WvMetricsSnapshot&&) = default;
WvMetricsSnapshot& operator=(const WvMetricsSnapshot&) = default;
WvMetricsSnapshot& operator=(WvMetricsSnapshot&&) = default;
const CdmIdentifier& cdm_id() const { return cdm_id_; }
const drm_metrics::WvCdmMetrics& metrics() const { return metrics_; }
::time_t timestamp() const { return timestamp_; }
std::string GetFormattedTimestamp() const;
private:
CdmIdentifier cdm_id_;
drm_metrics::WvCdmMetrics metrics_;
::time_t timestamp_ = 0;
};
class WvContentDecryptionModule : public android::RefBase, public TimerHandler {
public:
WvContentDecryptionModule();
@@ -142,18 +170,30 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler {
// Validate a passed-in service certificate
virtual bool IsValidServiceCertificate(const std::string& certificate);
// Fill the metrics parameter with the metrics data for the CdmEngine
// associated with the given CdmIdentifier. If the CdmEngine instance does
// not exist, this will return an error.
virtual CdmResponseType GetMetrics(const CdmIdentifier& identifier,
drm_metrics::WvCdmMetrics* metrics);
// Fill the |metrics| parameter with the engine metrics data for the
// CdmEngine associated with the given CdmIdentifier.
// If the CdmEngine instance does not exist, this will return an error.
virtual CdmResponseType GetCurrentMetrics(const CdmIdentifier& identifier,
drm_metrics::WvCdmMetrics* metrics);
// Fill the metrics parameter with the metrics data for all the CdmEngine
// associated with the given CdmIdentifiers. If there are no CdmEngine
// instances, this will return an error.
virtual CdmResponseType GetMetrics(
std::vector<drm_metrics::WvCdmMetrics>* metrics,
bool* full_list_returned);
// Fill the |snapshots| parameter wrapped engine metrics for all CdmEngine
// instances that currently enabled (actively decrypting or performing
// other engine-level operations).
// Current metrics snapshots contain the engine identifier, metrics data
// and time (roughly current time).
// |full_list_returned| will indicate whether all existing enginers were
// able to report their metrics successfully.
virtual CdmResponseType GetAllCurrentMetricsSnapshots(
std::vector<WvMetricsSnapshot>* snapshots, bool* full_list_returned);
// Fill the |snapshots| parameter wrapped engine metrics for several of
// the most recently closed CdmEngine instances.
// Saved metrics snapshots contain the engine identifier, metrics data
// and time of closing.
// The same engine identifier may appear multiple times in the list (
// depending on how the app utilizes the MediaDrm plugin).
virtual CdmResponseType GetAllSavedMetricsSnapshots(
std::vector<WvMetricsSnapshot>* snapshots);
// Closes the CdmEngine and sessions associated with the given CdmIdentifier.
virtual CdmResponseType CloseCdm(const CdmIdentifier& identifier);
@@ -234,8 +274,10 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler {
// not exist, this will return an error.
// This methods assumes that |metrics| is not null and that the |cdms_lock_|
// has already been acquired.
CdmResponseType GetMetricsInternal(const CdmIdentifier& identifier,
drm_metrics::WvCdmMetrics* metrics);
CdmResponseType GetCurrentMetricsInternal(const CdmIdentifier& identifier,
drm_metrics::WvCdmMetrics* metrics);
void SaveMetrics(const CdmIdentifier& identifier,
drm_metrics::WvCdmMetrics&& metrics);
static std::mutex session_sharing_id_generation_lock_;
std::mutex timer_lock_;
@@ -264,6 +306,10 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler {
// - Hold lock when erasing, release once erased.
std::mutex cdms_lock_;
// Contains a finite list of histories of different CDM engine instances.
// When a CDM engine is closed, its metrics will be saved.
std::deque<WvMetricsSnapshot> saved_metrics_snapshots_;
CORE_DISALLOW_COPY_AND_ASSIGN(WvContentDecryptionModule);
};
} // namespace wvcdm

View File

@@ -20,14 +20,71 @@
#include "wv_cdm_event_listener.h"
#include "wv_metrics.pb.h"
namespace wvcdm {
using wvutil::FileSystem;
using wvutil::LoggingUidSetter;
namespace {
const int kCdmTimerDurationSeconds = 1;
// In the interest of limiting memory usage, only a limited number of
// the most recent metrics snapshots whould be stored.
constexpr size_t kMaxSavedMetricsSnapshotCount = 50;
// Value returned by time() if the operation fails.
constexpr time_t kTimeError = static_cast<time_t>(-1);
// Removes certain field from the provided CdmIdentifier as to make
// it better usable for tracking the same engine across different
// calls into the DRM plugin service.
CdmIdentifier ScrubIdentifierForMetrics(const CdmIdentifier& identifier) {
return CdmIdentifier{identifier.spoid, identifier.origin,
identifier.app_package_name, identifier.unique_id,
/* user_id */ wvutil::UNKNOWN_UID};
}
} // namespace
namespace wvcdm {
WvMetricsSnapshot::WvMetricsSnapshot(const CdmIdentifier& identifier,
drm_metrics::WvCdmMetrics&& metrics,
time_t ts)
: cdm_id_(ScrubIdentifierForMetrics(identifier)),
metrics_(std::move(metrics)),
timestamp_(ts) {}
WvMetricsSnapshot WvMetricsSnapshot::MakeSnapshot(
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics&& metrics) {
const time_t now = time(nullptr);
if (now == kTimeError) {
LOGW("Failed to get current time");
return WvMetricsSnapshot(identifier, std::move(metrics), 0);
}
return WvMetricsSnapshot(identifier, std::move(metrics), now);
}
std::string WvMetricsSnapshot::GetFormattedTimestamp() const {
if (timestamp_ == 0) return "<unset>";
struct tm time_info = {};
if (localtime_r(&timestamp_, &time_info) == nullptr) {
LOGE("Failed to convert to local time");
return "<conversion error(" + std::to_string(timestamp_) + ")>";
}
char timestamp_buffer[64]; // asctime_r() requires at least 29 bytes.
memset(timestamp_buffer, 0, sizeof(timestamp_buffer));
if (asctime_r(&time_info, timestamp_buffer) == nullptr) {
LOGE("Failed to format localtime to ASC time");
return "<format error(" + std::to_string(timestamp_) + ")>";
}
std::string result(timestamp_buffer);
// The Linux manual illustrates asctime_r() as returning a value
// with a new line character at the end. However, it does not
// specify this in the description. This is to account for different
// behavior on different systems.
if (!result.empty() && result.back() == '\n') {
result.pop_back();
}
return result;
}
std::mutex WvContentDecryptionModule::session_sharing_id_generation_lock_;
@@ -394,45 +451,61 @@ bool WvContentDecryptionModule::IsValidServiceCertificate(
return cert.has_certificate();
}
CdmResponseType WvContentDecryptionModule::GetMetrics(
std::vector<drm_metrics::WvCdmMetrics>* metrics, bool* full_list_returned) {
if (!metrics || !full_list_returned) {
return CdmResponseType(PARAMETER_NULL);
}
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
for (auto& key_value_pair : cdms_) {
const CdmIdentifier& identifier = key_value_pair.first;
drm_metrics::WvCdmMetrics metric;
const CdmResponseType status = GetMetricsInternal(identifier, &metric);
if (status == NO_ERROR) {
metrics->push_back(metric);
} else {
LOGD("GetMetrics call failed: cdm identifier=%u, error=%d",
identifier.unique_id, status.ToInt());
}
}
// With no streaming activities, |cdms_| size would be zero,
// treat it as a non full list in that case.
*full_list_returned = !cdms_.empty() && metrics->size() == cdms_.size();
// We only return error if no metrics is returned when cdms_ is not empty.
// - metrics && cdms_ sizes can both be zero, returns NO_ERROR
// - metrics size <= cdms_, returns NO_ERROR
// - metrics is empty, but cdms_ is not, returns UNKNOWN_ERROR
return (metrics->empty() && !cdms_.empty()) ? CdmResponseType(UNKNOWN_ERROR)
: CdmResponseType(NO_ERROR);
}
CdmResponseType WvContentDecryptionModule::GetMetrics(
CdmResponseType WvContentDecryptionModule::GetCurrentMetrics(
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics* metrics) {
if (!metrics) {
return CdmResponseType(PARAMETER_NULL);
}
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
return GetMetricsInternal(identifier, metrics);
return GetCurrentMetricsInternal(identifier, metrics);
}
CdmResponseType WvContentDecryptionModule::GetMetricsInternal(
CdmResponseType WvContentDecryptionModule::GetAllCurrentMetricsSnapshots(
std::vector<WvMetricsSnapshot>* snapshots, bool* full_list_returned) {
if (!snapshots || !full_list_returned) {
return CdmResponseType(PARAMETER_NULL);
}
snapshots->clear();
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
for (const auto& cdm_info : cdms_) {
const CdmIdentifier& identifier = cdm_info.first;
drm_metrics::WvCdmMetrics metric;
const CdmResponseType status =
GetCurrentMetricsInternal(identifier, &metric);
if (status == NO_ERROR) {
snapshots->push_back(
WvMetricsSnapshot::MakeSnapshot(identifier, std::move(metric)));
} else {
LOGD("Failed to get metrics: cdm_identifier = %u, error = %s",
identifier.unique_id, status.ToString().c_str());
}
}
// With no streaming activities, |cdms_| size would be zero,
// treat it as a non full list in that case.
*full_list_returned = !cdms_.empty() && snapshots->size() == cdms_.size();
// We only return error if no metrics is returned when cdms_ is not empty.
// - metrics && cdms_ sizes can both be zero, returns NO_ERROR
// - metrics size <= cdms_, returns NO_ERROR
// - metrics is empty, but cdms_ is not, returns UNKNOWN_ERROR
return (snapshots->empty() && !cdms_.empty()) ? CdmResponseType(UNKNOWN_ERROR)
: CdmResponseType(NO_ERROR);
}
CdmResponseType WvContentDecryptionModule::GetAllSavedMetricsSnapshots(
std::vector<WvMetricsSnapshot>* snapshots) {
if (snapshots == nullptr) {
return CdmResponseType(PARAMETER_NULL);
}
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
// This has the potential to be an expensive operation. However,
// this function is only used during debug reporting.
snapshots->assign(saved_metrics_snapshots_.cbegin(),
saved_metrics_snapshots_.cend());
return CdmResponseType(NO_ERROR);
}
CdmResponseType WvContentDecryptionModule::GetCurrentMetricsInternal(
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics* metrics) {
// Note: Caller should lock before calling.
auto it = cdms_.find(identifier);
@@ -446,6 +519,16 @@ CdmResponseType WvContentDecryptionModule::GetMetricsInternal(
: CdmResponseType(UNKNOWN_ERROR);
}
void WvContentDecryptionModule::SaveMetrics(
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics&& metrics) {
// Caller should have acquired |cdms_lock_|.
saved_metrics_snapshots_.push_front(
WvMetricsSnapshot::MakeSnapshot(identifier, std::move(metrics)));
if (saved_metrics_snapshots_.size() > kMaxSavedMetricsSnapshotCount) {
saved_metrics_snapshots_.resize(kMaxSavedMetricsSnapshotCount);
}
}
WvContentDecryptionModule::CdmInfo::CdmInfo()
: cdm_engine(CdmEngineFactory::CreateCdmEngine(&file_system)) {}
@@ -489,6 +572,17 @@ CdmEngine* WvContentDecryptionModule::GetCdmForSessionId(
void WvContentDecryptionModule::CloseAllCdms() {
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
for (const auto& cdm_info : cdms_) {
const CdmIdentifier& identifier = cdm_info.first;
drm_metrics::WvCdmMetrics metrics;
const CdmResponseType status =
GetCurrentMetricsInternal(identifier, &metrics);
if (status == NO_ERROR) {
SaveMetrics(identifier, std::move(metrics));
} else {
LOGD("CloseAllCdms failed to save metrics");
}
}
cdm_by_session_id_.clear();
cdms_.clear();
}
@@ -496,22 +590,32 @@ void WvContentDecryptionModule::CloseAllCdms() {
CdmResponseType WvContentDecryptionModule::CloseCdm(
const CdmIdentifier& cdm_identifier) {
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
auto it = cdms_.find(cdm_identifier);
if (it == cdms_.end()) {
auto cdm_it = cdms_.find(cdm_identifier);
if (cdm_it == cdms_.end()) {
LOGE("Cdm Identifier not found");
// TODO(blueeyes): Create a better error.
return CdmResponseType(UNKNOWN_ERROR);
}
CdmEngine* cdm_engine = cdm_it->second.cdm_engine.get();
// Save metrics snapshot.
drm_metrics::WvCdmMetrics metrics;
const bool success = cdm_engine->GetMetricsSnapshot(&metrics);
if (success) {
SaveMetrics(cdm_identifier, std::move(metrics));
} else {
LOGW("Failed to get metrics snapshot: unique_id = %" PRIu32,
cdm_identifier.unique_id);
}
// Remove any sessions that point to this engine.
for (auto session_it = cdm_by_session_id_.begin();
session_it != cdm_by_session_id_.end();) {
if (session_it->second == it->second.cdm_engine.get()) {
if (session_it->second == cdm_engine) {
session_it = cdm_by_session_id_.erase(session_it);
} else {
++session_it;
}
}
cdms_.erase(it);
cdms_.erase(cdm_it);
return CdmResponseType(NO_ERROR);
}

View File

@@ -1545,7 +1545,8 @@ TEST_P(WvCdmStreamingUsageReportTest, DISABLED_UsageTest) {
// Validate that update usage table entry is exercised.
drm_metrics::WvCdmMetrics metrics;
ASSERT_EQ(NO_ERROR, decryptor_->GetMetrics(kDefaultCdmIdentifier, &metrics));
ASSERT_EQ(NO_ERROR, decryptor_->GetCurrentMetrics(
kDefaultCdmIdentifier, &metrics));
ValidateHasUpdateUsageEntry(metrics);
}

View File

@@ -45,7 +45,7 @@ class WvContentDecryptionModuleMetricsTest : public WvCdmTestBase {
TEST_F(WvContentDecryptionModuleMetricsTest, IdentifierNotFound) {
drm_metrics::WvCdmMetrics metrics;
ASSERT_EQ(wvcdm::UNKNOWN_ERROR,
decryptor_.GetMetrics(kDefaultCdmIdentifier, &metrics));
decryptor_.GetCurrentMetrics(kDefaultCdmIdentifier, &metrics));
}
TEST_F(WvContentDecryptionModuleMetricsTest, EngineOnlyMetrics) {
@@ -62,7 +62,7 @@ TEST_F(WvContentDecryptionModuleMetricsTest, EngineOnlyMetrics) {
drm_metrics::WvCdmMetrics metrics;
ASSERT_EQ(wvcdm::NO_ERROR,
decryptor_.GetMetrics(kDefaultCdmIdentifier, &metrics));
decryptor_.GetCurrentMetrics(kDefaultCdmIdentifier, &metrics));
// 100 is an arbitrary high value that shouldn't ever occur.
EXPECT_THAT(metrics.engine_metrics()
@@ -110,7 +110,7 @@ TEST_F(WvContentDecryptionModuleMetricsTest, EngineAndSessionMetrics) {
drm_metrics::WvCdmMetrics metrics;
ASSERT_EQ(wvcdm::NO_ERROR,
decryptor_.GetMetrics(kDefaultCdmIdentifier, &metrics));
decryptor_.GetCurrentMetrics(kDefaultCdmIdentifier, &metrics));
std::string serialized_metrics;
ASSERT_TRUE(metrics.SerializeToString(&serialized_metrics));
@@ -162,7 +162,8 @@ TEST_F(WvContentDecryptionModuleMetricsTest,
for (int i = 0; i < cdm_engine_count; i++) {
drm_metrics::WvCdmMetrics metrics;
metrics.Clear();
ASSERT_EQ(wvcdm::NO_ERROR, decryptor_.GetMetrics(identifiers[i], &metrics));
ASSERT_EQ(wvcdm::NO_ERROR,
decryptor_.GetCurrentMetrics(identifiers[i], &metrics));
std::string serialized_metrics;
ASSERT_TRUE(metrics.SerializeToString(&serialized_metrics));