Commit 4f0df039 authored by hbos's avatar hbos Committed by Commit bot

Preparation CL for WebRTC performance test using promise-based getStats

Re-land of https://codereview.chromium.org/2534633002/

In order to have a perf test looking at stats of interest we need to be
able to get the results of a JavaScript getStats call. These can be
returned as JSON-stringified JavaScript dictionaries.

In this CL:
- [g|G]etStatsReportDictionary added to webrtc_browsertest_base.cc and
  peerconnection_getstats.js, which produce/read a JSON-stringified
  version of the stats.
- RTCStatsReportDictionary and RTCStatsDictionary, helper classes for
  reading stats from the base::Dictionary representation of the stats.
- Unittests for the helper classes.

In a follow-up CL these methods/classes will be used to obtain the
stats in the to-be-added performance test. This will yield pretty
graphs.

BUG=670306, 627816

Review-Url: https://codereview.chromium.org/2543173002
Cr-Commit-Position: refs/heads/master@{#436252}
parent b9041776
// Copyright 2016 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/media/webrtc/test_stats_dictionary.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
namespace content {
TestStatsReportDictionary::TestStatsReportDictionary(
std::unique_ptr<base::DictionaryValue> report)
: report_(std::move(report)) {
CHECK(report_);
}
TestStatsReportDictionary::~TestStatsReportDictionary() {
}
void TestStatsReportDictionary::ForEach(
std::function<void(const TestStatsDictionary&)> iteration) {
for (base::DictionaryValue::Iterator it(*report_); !it.IsAtEnd();
it.Advance()) {
const base::DictionaryValue* it_value;
CHECK(it.value().GetAsDictionary(&it_value));
iteration(TestStatsDictionary(this, it_value));
}
}
std::vector<TestStatsDictionary> TestStatsReportDictionary::Filter(
std::function<bool(const TestStatsDictionary&)> filter) {
std::vector<TestStatsDictionary> result;
ForEach([&result, &filter](const TestStatsDictionary& stats) {
if (filter(stats))
result.push_back(stats);
});
return result;
}
std::unique_ptr<TestStatsDictionary> TestStatsReportDictionary::Get(
const std::string& id) {
const base::DictionaryValue* dictionary;
if (!report_->GetDictionary(id, &dictionary))
return nullptr;
return std::unique_ptr<TestStatsDictionary>(
new TestStatsDictionary(this, dictionary));
}
std::vector<TestStatsDictionary> TestStatsReportDictionary::GetAll() {
return Filter([](const TestStatsDictionary&) { return true; });
}
std::vector<TestStatsDictionary> TestStatsReportDictionary::GetByType(
const std::string& type) {
return Filter([&type](const TestStatsDictionary& stats) {
return stats.GetString("type") == type;
});
}
TestStatsDictionary::TestStatsDictionary(
TestStatsReportDictionary* report, const base::DictionaryValue* stats)
: report_(report), stats_(stats) {
CHECK(report_);
CHECK(stats_);
}
TestStatsDictionary::TestStatsDictionary(
const TestStatsDictionary& other) = default;
TestStatsDictionary::~TestStatsDictionary() {
}
bool TestStatsDictionary::IsBoolean(const std::string& key) const {
bool value;
return GetBoolean(key, &value);
}
bool TestStatsDictionary::GetBoolean(const std::string& key) const {
bool value;
CHECK(GetBoolean(key, &value));
return value;
}
bool TestStatsDictionary::IsNumber(const std::string& key) const {
double value;
return GetNumber(key, &value);
}
double TestStatsDictionary::GetNumber(const std::string& key) const {
double value;
CHECK(GetNumber(key, &value));
return value;
}
bool TestStatsDictionary::IsString(const std::string& key) const {
std::string value;
return GetString(key, &value);
}
std::string TestStatsDictionary::GetString(const std::string& key) const {
std::string value;
CHECK(GetString(key, &value));
return value;
}
bool TestStatsDictionary::IsSequenceBoolean(const std::string& key) const {
std::vector<bool> value;
return GetSequenceBoolean(key, &value);
}
std::vector<bool> TestStatsDictionary::GetSequenceBoolean(
const std::string& key) const {
std::vector<bool> value;
CHECK(GetSequenceBoolean(key, &value));
return value;
}
bool TestStatsDictionary::IsSequenceNumber(const std::string& key) const {
std::vector<double> value;
return GetSequenceNumber(key, &value);
}
std::vector<double> TestStatsDictionary::GetSequenceNumber(
const std::string& key) const {
std::vector<double> value;
CHECK(GetSequenceNumber(key, &value));
return value;
}
bool TestStatsDictionary::IsSequenceString(const std::string& key) const {
std::vector<std::string> value;
return GetSequenceString(key, &value);
}
std::vector<std::string> TestStatsDictionary::GetSequenceString(
const std::string& key) const {
std::vector<std::string> value;
CHECK(GetSequenceString(key, &value));
return value;
}
bool TestStatsDictionary::GetBoolean(
const std::string& key, bool* out) const {
return stats_->GetBoolean(key, out);
}
bool TestStatsDictionary::GetNumber(
const std::string& key, double* out) const {
return stats_->GetDouble(key, out);
}
bool TestStatsDictionary::GetString(
const std::string& key, std::string* out) const {
return stats_->GetString(key, out);
}
bool TestStatsDictionary::GetSequenceBoolean(
const std::string& key,
std::vector<bool>* out) const {
const base::ListValue* list;
if (!stats_->GetList(key, &list))
return false;
std::vector<bool> sequence;
bool element;
for (size_t i = 0; i < list->GetSize(); ++i) {
if (!list->GetBoolean(i, &element))
return false;
sequence.push_back(element);
}
*out = std::move(sequence);
return true;
}
bool TestStatsDictionary::GetSequenceNumber(
const std::string& key,
std::vector<double>* out) const {
const base::ListValue* list;
if (!stats_->GetList(key, &list))
return false;
std::vector<double> sequence;
double element;
for (size_t i = 0; i < list->GetSize(); ++i) {
if (!list->GetDouble(i, &element))
return false;
sequence.push_back(element);
}
*out = std::move(sequence);
return true;
}
bool TestStatsDictionary::GetSequenceString(
const std::string& key,
std::vector<std::string>* out) const {
const base::ListValue* list;
if (!stats_->GetList(key, &list))
return false;
std::vector<std::string> sequence;
std::string element;
for (size_t i = 0; i < list->GetSize(); ++i) {
if (!list->GetString(i, &element))
return false;
sequence.push_back(element);
}
*out = std::move(sequence);
return true;
}
std::string TestStatsDictionary::ToString() const {
std::string str;
CHECK(base::JSONWriter::WriteWithOptions(
*stats_, base::JSONWriter::OPTIONS_PRETTY_PRINT, &str));
return str;
}
} // namespace content
// Copyright 2016 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.
#ifndef CHROME_BROWSER_MEDIA_WEBRTC_TEST_STATS_DICTIONARY_H_
#define CHROME_BROWSER_MEDIA_WEBRTC_TEST_STATS_DICTIONARY_H_
#include <functional>
#include <string>
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/values.h"
namespace content {
class TestStatsDictionary;
class TestStatsReportDictionary
: public base::RefCounted<TestStatsReportDictionary> {
public:
explicit TestStatsReportDictionary(
std::unique_ptr<base::DictionaryValue> report);
void ForEach(std::function<void(const TestStatsDictionary&)> iteration);
std::vector<TestStatsDictionary> Filter(
std::function<bool(const TestStatsDictionary&)> filter);
std::unique_ptr<TestStatsDictionary> Get(const std::string& id);
std::vector<TestStatsDictionary> GetAll();
std::vector<TestStatsDictionary> GetByType(const std::string& type);
private:
friend class base::RefCounted<TestStatsReportDictionary>;
~TestStatsReportDictionary();
std::unique_ptr<base::DictionaryValue> report_;
};
class TestStatsDictionary {
public:
TestStatsDictionary(TestStatsReportDictionary* report,
const base::DictionaryValue* stats);
TestStatsDictionary(const TestStatsDictionary& other);
~TestStatsDictionary();
bool IsBoolean(const std::string& key) const;
bool GetBoolean(const std::string& key) const;
bool IsNumber(const std::string& key) const;
double GetNumber(const std::string& key) const;
bool IsString(const std::string& key) const;
std::string GetString(const std::string& key) const;
bool IsSequenceBoolean(const std::string& key) const;
std::vector<bool> GetSequenceBoolean(const std::string& key) const;
bool IsSequenceNumber(const std::string& key) const;
std::vector<double> GetSequenceNumber(const std::string& key) const;
bool IsSequenceString(const std::string& key) const;
std::vector<std::string> GetSequenceString(const std::string& key) const;
std::string ToString() const;
private:
bool GetBoolean(const std::string& key, bool* out) const;
bool GetNumber(const std::string& key, double* out) const;
bool GetString(const std::string& key, std::string* out) const;
bool GetSequenceBoolean(
const std::string& key, std::vector<bool>* out) const;
bool GetSequenceNumber(
const std::string& key, std::vector<double>* out) const;
bool GetSequenceString(
const std::string& key, std::vector<std::string>* out) const;
// The reference keeps the report alive which indirectly owns |stats_|.
scoped_refptr<TestStatsReportDictionary> report_;
const base::DictionaryValue* stats_;
};
} // namespace content
#endif // CHROME_BROWSER_MEDIA_WEBRTC_TEST_STATS_DICTIONARY_H_
// Copyright 2016 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/media/webrtc/test_stats_dictionary.h"
#include <memory>
#include <set>
#include <vector>
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/values.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
const char kTestStatsReportJson[] =
R"({
"GarbageA": {
"id": "GarbageA",
"timestamp": 0.0,
"type": "garbage"
},
"RTCTestStatsID": {
"id": "RTCTestStatsID",
"timestamp": 13.37,
"type": "test",
"boolean": true,
"number": 42,
"string": "text",
"sequenceBoolean": [ true ],
"sequenceNumber": [ 42 ],
"sequenceString": [ "text" ]
},
"GarbageB": {
"id": "GarbageB",
"timestamp": 0.0,
"type": "garbage"
}
})";
class TestStatsDictionaryTest : public testing::Test {
public:
TestStatsDictionaryTest() {
std::unique_ptr<base::Value> value =
base::JSONReader::Read(kTestStatsReportJson);
CHECK(value);
base::DictionaryValue* dictionary;
CHECK(value->GetAsDictionary(&dictionary));
ignore_result(value.release());
report_ = new TestStatsReportDictionary(
std::unique_ptr<base::DictionaryValue>(dictionary));
}
protected:
scoped_refptr<TestStatsReportDictionary> report_;
};
TEST_F(TestStatsDictionaryTest, ReportGetStats) {
EXPECT_FALSE(report_->Get("InvalidID"));
EXPECT_TRUE(report_->Get("GarbageA"));
EXPECT_TRUE(report_->Get("RTCTestStatsID"));
EXPECT_TRUE(report_->Get("GarbageB"));
}
TEST_F(TestStatsDictionaryTest, ReportForEach) {
std::set<std::string> remaining;
remaining.insert("GarbageA");
remaining.insert("RTCTestStatsID");
remaining.insert("GarbageB");
report_->ForEach([&remaining](const TestStatsDictionary& stats) {
remaining.erase(stats.GetString("id"));
});
EXPECT_TRUE(remaining.empty());
}
TEST_F(TestStatsDictionaryTest, ReportFilterStats) {
std::vector<TestStatsDictionary> filtered_stats = report_->Filter(
[](const TestStatsDictionary& stats) -> bool {
return false;
});
EXPECT_EQ(filtered_stats.size(), 0u);
filtered_stats = report_->Filter(
[](const TestStatsDictionary& stats) -> bool {
return true;
});
EXPECT_EQ(filtered_stats.size(), 3u);
filtered_stats = report_->Filter(
[](const TestStatsDictionary& stats) -> bool {
return stats.GetString("id") == "RTCTestStatsID";
});
EXPECT_EQ(filtered_stats.size(), 1u);
}
TEST_F(TestStatsDictionaryTest, ReportGetAll) {
std::set<std::string> remaining;
remaining.insert("GarbageA");
remaining.insert("RTCTestStatsID");
remaining.insert("GarbageB");
for (const TestStatsDictionary& stats : report_->GetAll()) {
remaining.erase(stats.GetString("id"));
}
EXPECT_TRUE(remaining.empty());
}
TEST_F(TestStatsDictionaryTest, ReportGetByType) {
std::vector<TestStatsDictionary> stats = report_->GetByType("garbage");
EXPECT_EQ(stats.size(), 2u);
std::set<std::string> remaining;
remaining.insert("GarbageA");
remaining.insert("GarbageB");
report_->ForEach([&remaining](const TestStatsDictionary& stats) {
remaining.erase(stats.GetString("id"));
});
EXPECT_TRUE(remaining.empty());
}
TEST_F(TestStatsDictionaryTest, StatsVerifyMembers) {
std::unique_ptr<TestStatsDictionary> stats = report_->Get("RTCTestStatsID");
EXPECT_TRUE(stats);
EXPECT_FALSE(stats->IsBoolean("nonexistentMember"));
EXPECT_FALSE(stats->IsNumber("nonexistentMember"));
EXPECT_FALSE(stats->IsString("nonexistentMember"));
EXPECT_FALSE(stats->IsSequenceBoolean("nonexistentMember"));
EXPECT_FALSE(stats->IsSequenceNumber("nonexistentMember"));
EXPECT_FALSE(stats->IsSequenceString("nonexistentMember"));
ASSERT_TRUE(stats->IsBoolean("boolean"));
EXPECT_EQ(stats->GetBoolean("boolean"), true);
ASSERT_TRUE(stats->IsNumber("number"));
EXPECT_EQ(stats->GetNumber("number"), 42.0);
ASSERT_TRUE(stats->IsString("string"));
EXPECT_EQ(stats->GetString("string"), "text");
ASSERT_TRUE(stats->IsSequenceBoolean("sequenceBoolean"));
EXPECT_EQ(stats->GetSequenceBoolean("sequenceBoolean"),
std::vector<bool> { true });
ASSERT_TRUE(stats->IsSequenceNumber("sequenceNumber"));
EXPECT_EQ(stats->GetSequenceNumber("sequenceNumber"),
std::vector<double> { 42.0 });
ASSERT_TRUE(stats->IsSequenceString("sequenceString"));
EXPECT_EQ(stats->GetSequenceString("sequenceString"),
std::vector<std::string> { "text" });
}
TEST_F(TestStatsDictionaryTest, TestStatsDictionaryShouldKeepReportAlive) {
std::unique_ptr<TestStatsDictionary> stats = report_->Get("RTCTestStatsID");
EXPECT_TRUE(stats);
report_ = nullptr;
EXPECT_EQ(stats->GetString("string"), "text");
}
} // namespace
} // namespace content
......@@ -8,9 +8,11 @@
#include "base/json/json_reader.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
#include "chrome/browser/permissions/permission_request_manager.h"
......@@ -494,6 +496,21 @@ std::vector<std::string> WebRtcTestBase::VerifyStatsGeneratedPromise(
return JsonArrayToVectorOfStrings(result.substr(3));
}
scoped_refptr<content::TestStatsReportDictionary>
WebRtcTestBase::GetStatsReportDictionary(content::WebContents* tab) const {
std::string result = ExecuteJavascript("getStatsReportDictionary()", tab);
EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
std::unique_ptr<base::Value> parsed_json = base::JSONReader::Read(
result.substr(3));
base::DictionaryValue* dictionary;
CHECK(parsed_json);
CHECK(parsed_json->GetAsDictionary(&dictionary));
ignore_result(parsed_json.release());
return scoped_refptr<content::TestStatsReportDictionary>(
new content::TestStatsReportDictionary(
std::unique_ptr<base::DictionaryValue>(dictionary)));
}
std::vector<std::string> WebRtcTestBase::GetWhitelistedStatsTypes(
content::WebContents* tab) const {
return JsonArrayToVectorOfStrings(
......
......@@ -6,8 +6,11 @@
#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_BASE_H_
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "chrome/browser/media/webrtc/test_stats_dictionary.h"
#include "chrome/test/base/in_process_browser_test.h"
namespace infobars {
......@@ -166,6 +169,8 @@ class WebRtcTestBase : public InProcessBrowserTest {
void VerifyStatsGeneratedCallback(content::WebContents* tab) const;
std::vector<std::string> VerifyStatsGeneratedPromise(
content::WebContents* tab) const;
scoped_refptr<content::TestStatsReportDictionary> GetStatsReportDictionary(
content::WebContents* tab) const;
std::vector<std::string> GetWhitelistedStatsTypes(
content::WebContents* tab) const;
......
......@@ -1586,6 +1586,9 @@ test("browser_tests") {
"../browser/media/test_license_server_config.h",
"../browser/media/webrtc/media_stream_devices_controller_browsertest.cc",
"../browser/media/webrtc/media_stream_infobar_browsertest.cc",
"../browser/media/webrtc/test_stats_dictionary.cc",
"../browser/media/webrtc/test_stats_dictionary.h",
"../browser/media/webrtc/test_stats_dictionary_unittest.cc",
"../browser/media/webrtc/webrtc_apprtc_browsertest.cc",
"../browser/media/webrtc/webrtc_audio_quality_browsertest.cc",
"../browser/media/webrtc/webrtc_browsertest.cc",
......
......@@ -260,6 +260,29 @@ function verifyStatsGeneratedPromise() {
});
}
/**
* Gets the result of the promise-based |RTCPeerConnection.getStats| as a
* dictionary of RTCStats-dictionaries.
*
* Returns "ok-" followed by a JSON-stringified dictionary of dictionaries to
* the test.
*/
function getStatsReportDictionary() {
peerConnection_().getStats()
.then(function(report) {
if (report == null || report.size == 0)
throw new failTest('report is null or empty.');
let reportDictionary = {};
for (let stats of report.values()) {
reportDictionary[stats.id] = stats;
}
returnToTest('ok-' + JSON.stringify(reportDictionary));
},
function(e) {
throw failTest('Promise was rejected: ' + e);
});
}
/**
* Returns a complete list of whitelisted "RTCStats.type" values as a
* JSON-stringified array of strings to the test.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment