Commit 0b937ecc authored by Douglas Creager's avatar Douglas Creager Committed by Commit Bot

Reporting: Return base::Value view of cache contents

This JSON copy of the data will let us show the current contents of the
Reporting cache in the net-internals page.

Bug: 829353
Change-Id: I005f642e506d9c70e58c1b5799263931327c4a1f
Reviewed-on: https://chromium-review.googlesource.com/996883
Commit-Queue: Douglas Creager <dcreager@google.com>
Reviewed-by: default avatarJulia Tuttle <juliatuttle@chromium.org>
Cr-Commit-Position: refs/heads/master@{#550612}
parent b199ba71
......@@ -4,6 +4,7 @@
#include "net/reporting/reporting_cache.h"
#include <algorithm>
#include <map>
#include <set>
#include <string>
......@@ -15,6 +16,7 @@
#include "base/stl_util.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "net/log/net_log.h"
#include "net/reporting/reporting_client.h"
#include "net/reporting/reporting_context.h"
#include "net/reporting/reporting_report.h"
......@@ -106,6 +108,49 @@ class ReportingCacheImpl : public ReportingCache {
}
}
base::Value GetReportsAsValue() const override {
// Sort the queued reports by origin and timestamp.
std::vector<const ReportingReport*> sorted_reports;
sorted_reports.reserve(reports_.size());
for (const auto& it : reports_) {
sorted_reports.push_back(it.second.get());
}
std::sort(
sorted_reports.begin(), sorted_reports.end(),
[](const ReportingReport* report1, const ReportingReport* report2) {
if (report1->queued < report2->queued)
return true;
else if (report1->queued > report2->queued)
return false;
else
return report1->url < report2->url;
});
std::vector<base::Value> report_list;
for (const ReportingReport* report : sorted_reports) {
base::Value report_dict(base::Value::Type::DICTIONARY);
report_dict.SetKey("url", base::Value(report->url.spec()));
report_dict.SetKey("group", base::Value(report->group));
report_dict.SetKey("type", base::Value(report->type));
report_dict.SetKey("depth", base::Value(report->depth));
report_dict.SetKey(
"queued", base::Value(NetLog::TickCountToString(report->queued)));
report_dict.SetKey("attempts", base::Value(report->attempts));
if (report->body) {
report_dict.SetKey("body", report->body->Clone());
}
if (base::ContainsKey(doomed_reports_, report)) {
report_dict.SetKey("status", base::Value("doomed"));
} else if (base::ContainsKey(pending_reports_, report)) {
report_dict.SetKey("status", base::Value("pending"));
} else {
report_dict.SetKey("status", base::Value("queued"));
}
report_list.push_back(std::move(report_dict));
}
return base::Value(std::move(report_list));
}
void GetNonpendingReports(
std::vector<const ReportingReport*>* reports_out) const override {
reports_out->clear();
......@@ -263,6 +308,70 @@ class ReportingCacheImpl : public ReportingCache {
clients_out->push_back(endpoint_and_client.second.get());
}
base::Value GetClientsAsValue() const override {
std::map<const url::Origin,
std::map<const std::string, std::vector<const ReportingClient*>>>
clients_by_origin_and_group;
for (const auto& it : clients_) {
const url::Origin& origin = it.first;
for (const auto& endpoint_and_client : it.second) {
const ReportingClient* client = endpoint_and_client.second.get();
clients_by_origin_and_group[origin][client->group].push_back(client);
}
}
std::vector<base::Value> origin_list;
for (const auto& it : clients_by_origin_and_group) {
const url::Origin& origin = it.first;
base::Value origin_dict(base::Value::Type::DICTIONARY);
origin_dict.SetKey("origin", base::Value(origin.Serialize()));
std::vector<base::Value> group_list;
for (const auto& group_and_clients : it.second) {
const std::string& group = group_and_clients.first;
const std::vector<const ReportingClient*>& clients =
group_and_clients.second;
base::Value group_dict(base::Value::Type::DICTIONARY);
group_dict.SetKey("name", base::Value(group));
std::vector<base::Value> endpoint_list;
for (const ReportingClient* client : clients) {
base::Value endpoint_dict(base::Value::Type::DICTIONARY);
// Reporting defines the group as a whole to have an expiration time,
// not the individual endpoints within the group.
group_dict.SetKey(
"expires",
base::Value(NetLog::TickCountToString(client->expires)));
endpoint_dict.SetKey("url", base::Value(client->endpoint.spec()));
endpoint_dict.SetKey("priority", base::Value(client->priority));
endpoint_dict.SetKey("weight", base::Value(client->weight));
auto metadata_it = client_metadata_.find(client);
if (metadata_it != client_metadata_.end()) {
const ClientStatistics& stats = metadata_it->second.stats;
base::Value successful_dict(base::Value::Type::DICTIONARY);
successful_dict.SetKey("uploads",
base::Value(stats.successful_uploads));
successful_dict.SetKey("reports",
base::Value(stats.successful_reports));
endpoint_dict.SetKey("successful", std::move(successful_dict));
base::Value failed_dict(base::Value::Type::DICTIONARY);
failed_dict.SetKey("uploads",
base::Value(stats.attempted_uploads -
stats.successful_uploads));
failed_dict.SetKey("reports",
base::Value(stats.attempted_reports -
stats.successful_reports));
endpoint_dict.SetKey("failed", std::move(failed_dict));
}
endpoint_list.push_back(std::move(endpoint_dict));
}
group_dict.SetKey("endpoints", base::Value(std::move(endpoint_list)));
group_list.push_back(std::move(group_dict));
}
origin_dict.SetKey("groups", base::Value(std::move(group_list)));
origin_list.push_back(std::move(origin_dict));
}
return base::Value(std::move(origin_list));
}
void GetClientsForOriginAndGroup(
const url::Origin& origin,
const std::string& group,
......
......@@ -76,6 +76,10 @@ class NET_EXPORT ReportingCache {
virtual void GetReports(
std::vector<const ReportingReport*>* reports_out) const = 0;
// Gets all reports in the cache, including pending and doomed reports, as a
// base::Value.
virtual base::Value GetReportsAsValue() const = 0;
// Gets all reports in the cache that aren't pending. The returned pointers
// are valid as long as either no calls to |RemoveReports| have happened or
// the reports' |pending| flag has been set to true using |SetReportsPending|.
......@@ -139,6 +143,10 @@ class NET_EXPORT ReportingCache {
virtual void GetClients(
std::vector<const ReportingClient*>* clients_out) const = 0;
// Gets information about all of the clients in the cache, encoded as a
// base::Value.
virtual base::Value GetClientsAsValue() const = 0;
// Gets all of the clients configured for a particular origin in a particular
// group. The returned pointers are only guaranteed to be valid if no calls
// have been made to |SetClient| or |RemoveEndpoint| in between.
......
......@@ -4,10 +4,12 @@
#include "net/reporting/reporting_cache.h"
#include <algorithm>
#include <string>
#include "base/strings/stringprintf.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "net/reporting/reporting_client.h"
......@@ -72,7 +74,51 @@ class ReportingCacheTest : public ReportingTestBase {
ReportingClient::kDefaultWeight);
}
// Adds a new report to the cache, and returns it.
const ReportingReport* AddAndReturnReport(
const GURL& url,
const std::string& group,
const std::string& type,
std::unique_ptr<const base::Value> body,
int depth,
base::TimeTicks queued,
int attempts) {
const base::Value* body_unowned = body.get();
// The public API will only give us the (unordered) full list of reports in
// the cache. So we need to grab the list before we add, and the list after
// we add, and return the one element that's different. This is only used
// in test cases, so I've optimized for readability over execution speed.
std::vector<const ReportingReport*> before;
cache()->GetReports(&before);
cache()->AddReport(url, group, type, std::move(body), depth, queued,
attempts);
std::vector<const ReportingReport*> after;
cache()->GetReports(&after);
for (const ReportingReport* report : after) {
// If report isn't in before, we've found the new instance.
if (std::find(before.begin(), before.end(), report) == before.end()) {
// Sanity check the result before we return it.
EXPECT_EQ(url, report->url);
EXPECT_EQ(group, report->group);
EXPECT_EQ(type, report->type);
EXPECT_EQ(*body_unowned, *report->body);
EXPECT_EQ(depth, report->depth);
EXPECT_EQ(queued, report->queued);
EXPECT_EQ(attempts, report->attempts);
return report;
}
}
// This can actually happen! If the newly created report isn't in the after
// vector, that means that we had to evict a report, and the new report was
// the only one eligible for eviction!
return nullptr;
}
const GURL kUrl1_ = GURL("https://origin1/path");
const GURL kUrl2_ = GURL("https://origin2/path");
const url::Origin kOrigin1_ = url::Origin::Create(GURL("https://origin1/"));
const url::Origin kOrigin2_ = url::Origin::Create(GURL("https://origin2/"));
const GURL kEndpoint1_ = GURL("https://endpoint1/");
......@@ -206,6 +252,73 @@ TEST_F(ReportingCacheTest, RemoveAllPendingReports) {
EXPECT_EQ(0u, cache()->GetFullReportCountForTesting());
}
TEST_F(ReportingCacheTest, GetReportsAsValue) {
// We need a reproducible expiry timestamp for this test case.
const base::TimeTicks now = base::TimeTicks();
const ReportingReport* report1 = AddAndReturnReport(
kUrl1_, kGroup1_, kType_, std::make_unique<base::DictionaryValue>(), 0,
now + base::TimeDelta::FromSeconds(200), 0);
const ReportingReport* report2 = AddAndReturnReport(
kUrl1_, kGroup2, kType_, std::make_unique<base::DictionaryValue>(), 0,
now + base::TimeDelta::FromSeconds(100), 1);
cache()->AddReport(kUrl2_, kGroup1_, kType_,
std::make_unique<base::DictionaryValue>(), 2,
now + base::TimeDelta::FromSeconds(200), 0);
cache()->AddReport(kUrl1_, kGroup1_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
now + base::TimeDelta::FromSeconds(300), 0);
// Mark report1 as pending as report2 as doomed
cache()->SetReportsPending({report1, report2});
cache()->RemoveReports({report2}, ReportingReport::Outcome::UNKNOWN);
base::Value actual = cache()->GetReportsAsValue();
std::unique_ptr<base::Value> expected = base::test::ParseJson(R"json(
[
{
"url": "https://origin1/path",
"group": "group2",
"type": "default",
"status": "doomed",
"body": {},
"attempts": 1,
"depth": 0,
"queued": "100000",
},
{
"url": "https://origin1/path",
"group": "group1",
"type": "default",
"status": "pending",
"body": {},
"attempts": 0,
"depth": 0,
"queued": "200000",
},
{
"url": "https://origin2/path",
"group": "group1",
"type": "default",
"status": "queued",
"body": {},
"attempts": 0,
"depth": 2,
"queued": "200000",
},
{
"url": "https://origin1/path",
"group": "group1",
"type": "default",
"status": "queued",
"body": {},
"attempts": 0,
"depth": 0,
"queued": "300000",
},
]
)json");
EXPECT_EQ(*expected, actual);
}
TEST_F(ReportingCacheTest, Endpoints) {
SetClient(kOrigin1_, kEndpoint1_, false, kGroup1_, kExpires1_);
EXPECT_EQ(1, observer()->cache_update_count());
......@@ -291,6 +404,62 @@ TEST_F(ReportingCacheTest, RemoveClientsForEndpoint) {
EXPECT_TRUE(clients.empty());
}
TEST_F(ReportingCacheTest, GetClientsAsValue) {
// We need a reproducible expiry timestamp for this test case.
const base::TimeTicks expires =
base::TimeTicks() + base::TimeDelta::FromDays(7);
SetClient(kOrigin1_, kEndpoint1_, false, kGroup1_, expires);
SetClient(kOrigin2_, kEndpoint2_, false, kGroup1_, expires);
// Add some reports so that we can test the upload counts.
const ReportingReport* report1a = AddAndReturnReport(
kUrl1_, kGroup1_, kType_, std::make_unique<base::DictionaryValue>(), 0,
expires, 0);
const ReportingReport* report1b = AddAndReturnReport(
kUrl1_, kGroup1_, kType_, std::make_unique<base::DictionaryValue>(), 0,
expires, 1);
const ReportingReport* report2 = AddAndReturnReport(
kUrl2_, kGroup1_, kType_, std::make_unique<base::DictionaryValue>(), 0,
expires, 1);
cache()->IncrementEndpointDeliveries(kEndpoint1_, {report1a, report1b}, true);
cache()->IncrementEndpointDeliveries(kEndpoint2_, {report2}, false);
base::Value actual = cache()->GetClientsAsValue();
std::unique_ptr<base::Value> expected = base::test::ParseJson(R"json(
[
{
"origin": "https://origin1",
"groups": [
{
"name": "group1",
"expires": "604800000",
"endpoints": [
{"url": "https://endpoint1/", "priority": 0, "weight": 1,
"successful": {"uploads": 1, "reports": 2},
"failed": {"uploads": 0, "reports": 0}},
],
},
],
},
{
"origin": "https://origin2",
"groups": [
{
"name": "group1",
"expires": "604800000",
"endpoints": [
{"url": "https://endpoint2/", "priority": 0, "weight": 1,
"successful": {"uploads": 0, "reports": 0},
"failed": {"uploads": 1, "reports": 1}},
],
},
],
},
]
)json");
EXPECT_EQ(*expected, actual);
}
TEST_F(ReportingCacheTest, RemoveAllClients) {
SetClient(kOrigin1_, kEndpoint1_, false, kGroup1_, kExpires1_);
SetClient(kOrigin2_, kEndpoint2_, false, kGroup1_, kExpires1_);
......
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