Commit e9c07b8f authored by khmel@chromium.org's avatar khmel@chromium.org Committed by Commit Bot

arc: Store information about tracing in graphics model.

This adds serialization/deserialization of information about tracing
session in graphics model instead of attaching it per each session done.
This includes app title, icon, timestamp and platform. This allows it to
reuse when loaded offline.

TEST=Locally, unit test extended.
BUG=b/143532713

Change-Id: If8888e46c3327c725f15ea7dec28310f92e66920
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1898617Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Commit-Queue: Yury Khmel <khmel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#712676}
parent 6c035fe0
......@@ -9,6 +9,7 @@
#include <algorithm>
#include <set>
#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
......@@ -53,10 +54,15 @@ constexpr char kKeyBuffers[] = "buffers";
constexpr char kKeyChrome[] = "chrome";
constexpr char kKeyDuration[] = "duration";
constexpr char kKeyGlobalEvents[] = "global_events";
constexpr char kKeyIcon[] = "icon";
constexpr char kKeyInformation[] = "information";
constexpr char kKeyInput[] = "input";
constexpr char kKeyViews[] = "views";
constexpr char kKeyPlatform[] = "platform";
constexpr char kKeySystem[] = "system";
constexpr char kKeyTaskId[] = "task_id";
constexpr char kKeyTimestamp[] = "timestamp";
constexpr char kKeyTitle[] = "title";
constexpr char kAcquireBufferQuery[] =
"android:onMessageReceived/android:handleMessageInvalidate/"
......@@ -1520,6 +1526,20 @@ bool LoadEventsContainer(const base::Value* value,
return true;
}
bool ReadDuration(const base::Value* root, uint32_t* duration) {
const base::Value* duration_value = root->FindKey(kKeyDuration);
if (!duration_value ||
(!duration_value->is_double() && !duration_value->is_int())) {
return false;
}
*duration = duration_value->GetDouble();
if (*duration < 0)
return false;
return true;
}
} // namespace
ArcTracingGraphicsModel::BufferEvent::BufferEvent(BufferEventType type,
......@@ -1759,6 +1779,10 @@ void ArcTracingGraphicsModel::Reset() {
chrome_buffer_id_to_task_id_.clear();
system_model_.Reset();
duration_ = 0;
app_title_ = std::string();
app_icon_png_.clear();
platform_ = std::string();
timestamp_ = base::Time();
}
void ArcTracingGraphicsModel::VsyncTrim() {
......@@ -1827,8 +1851,23 @@ std::unique_ptr<base::DictionaryValue> ArcTracingGraphicsModel::Serialize()
// System.
root->SetKey(kKeySystem, system_model_.Serialize());
// Duration.
root->SetKey(kKeyDuration, base::Value(static_cast<double>(duration_)));
// Information
base::DictionaryValue information;
information.SetKey(kKeyDuration, base::Value(static_cast<double>(duration_)));
if (!platform_.empty())
information.SetKey(kKeyPlatform, base::Value(platform_));
if (!timestamp_.is_null())
information.SetKey(kKeyTimestamp, base::Value(timestamp_.ToJsTime()));
if (!app_title_.empty())
information.SetKey(kKeyTitle, base::Value(app_title_));
if (!app_icon_png_.empty()) {
const std::string png_data_as_string(
reinterpret_cast<const char*>(&app_icon_png_[0]), app_icon_png_.size());
std::string icon_content;
base::Base64Encode(png_data_as_string, &icon_content);
information.SetKey(kKeyIcon, base::Value(icon_content));
}
root->SetKey(kKeyInformation, std::move(information));
return root;
}
......@@ -1891,13 +1930,37 @@ bool ArcTracingGraphicsModel::LoadFromValue(const base::DictionaryValue& root) {
if (!system_model_.Load(root.FindKey(kKeySystem)))
return false;
const base::Value* duration = root.FindKey(kKeyDuration);
if (!duration || (!duration->is_double() && !duration->is_int()))
return false;
const base::Value* informaton =
root.FindKeyOfType(kKeyInformation, base::Value::Type::DICTIONARY);
if (informaton) {
if (!ReadDuration(informaton, &duration_))
return false;
duration_ = duration->GetDouble();
if (duration_ < 0)
return false;
const base::Value* platform_value =
informaton->FindKeyOfType(kKeyPlatform, base::Value::Type::STRING);
if (platform_value)
platform_ = platform_value->GetString();
const base::Value* title_value =
informaton->FindKeyOfType(kKeyTitle, base::Value::Type::STRING);
if (title_value)
app_title_ = title_value->GetString();
const base::Value* icon_value =
informaton->FindKeyOfType(kKeyIcon, base::Value::Type::STRING);
if (icon_value) {
std::string icon_content;
if (!base::Base64Decode(icon_value->GetString(), &icon_content))
return false;
app_icon_png_ =
std::vector<unsigned char>(icon_content.begin(), icon_content.end());
}
const base::Value* timestamp_value =
informaton->FindKeyOfType(kKeyTimestamp, base::Value::Type::DOUBLE);
if (timestamp_value)
timestamp_ = base::Time::FromJsTime(timestamp_value->GetDouble());
} else {
if (!ReadDuration(&root, &duration_))
return false;
}
return true;
}
......
......@@ -11,6 +11,7 @@
#include <vector>
#include "base/macros.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/chromeos/arc/tracing/arc_system_model.h"
......@@ -169,6 +170,12 @@ class ArcTracingGraphicsModel {
bool LoadFromValue(const base::DictionaryValue& root);
uint64_t duration() const { return duration_; }
base::Time timestamp() const { return timestamp_; }
const std::string& app_title() const { return app_title_; }
const std::vector<unsigned char>& app_icon_png() const {
return app_icon_png_;
}
const std::string& platform() const { return platform_; }
const ViewMap& view_buffers() const { return view_buffers_; }
......@@ -183,6 +190,13 @@ class ArcTracingGraphicsModel {
ArcSystemModel& system_model() { return system_model_; }
const ArcSystemModel& system_model() const { return system_model_; }
void set_timestamp(base::Time timestamp) { timestamp_ = timestamp; }
void set_app_title(const std::string& app_title) { app_title_ = app_title; }
void set_app_icon_png(const std::vector<unsigned char>& app_icon_png) {
app_icon_png_ = app_icon_png;
}
void set_platform(const std::string& platform) { platform_ = platform; }
void set_skip_structure_validation() { skip_structure_validation_ = true; }
private:
......@@ -209,6 +223,15 @@ class ArcTracingGraphicsModel {
EventsContainer input_;
// Total duration of this model.
uint32_t duration_ = 0;
// Title of the traced app.
std::string app_title_;
// PNG content of traced app.
std::vector<unsigned char> app_icon_png_;
// Tested platform, it includes board, and versions.
std::string platform_;
// Timestamp of tracing.
base::Time timestamp_;
// Map Chrome buffer id to task id.
std::map<std::string, int> chrome_buffer_id_to_task_id_;
// CPU event model.
......
......@@ -127,6 +127,10 @@ void EnsureGraphicsModelsEqual(const ArcTracingGraphicsModel& model1,
EXPECT_EQ(model1.view_buffers(), model2.view_buffers());
EXPECT_EQ(model1.system_model(), model2.system_model());
EXPECT_EQ(model1.duration(), model2.duration());
EXPECT_EQ(model1.app_title(), model2.app_title());
EXPECT_EQ(model1.app_icon_png(), model2.app_icon_png());
EXPECT_EQ(model1.timestamp(), model2.timestamp());
EXPECT_EQ(model1.platform(), model2.platform());
}
} // namespace
......@@ -512,6 +516,13 @@ TEST_F(ArcTracingModelTest, GraphicsModelLoadSerialize) {
std::unique_ptr<ArcTracingGraphicsModel> model =
LoadGraphicsModel("gm_good.json");
ASSERT_TRUE(model);
EXPECT_EQ("CrOS 12642.0.0 (Official Build) dev-channel eve",
model->platform());
EXPECT_EQ("Play Store", model->app_title());
EXPECT_FALSE(model->app_icon_png().empty());
EXPECT_EQ(base::Time::FromJsTime(1572898642036L), model->timestamp());
EXPECT_EQ(1000U, model->duration());
ArcTracingGraphicsModel test_model;
EXPECT_TRUE(test_model.LoadFromJson(model->SerializeToJson()));
EnsureGraphicsModelsEqual(*model, test_model);
......
......@@ -51,11 +51,9 @@ function setModelHeader(model) {
title += ' ';
title += new Date(model.information.timestamp).toLocaleString();
}
if (model.information.duration) {
title += ' ';
title += (model.information.duration * 0.000001).toFixed(2);
title += 's';
}
title += ' ';
title += (model.information.duration * 0.000001).toFixed(2);
title += 's';
if (model.information.platform) {
title += ' on ';
title += model.information.platform;
......@@ -77,6 +75,8 @@ function setGraphicBuffersModel(model) {
setModelHeader(model);
var duration = model.information.duration;
// Microseconds per pixel. 100% zoom corresponds to 100 mcs per pixel.
var resolution = zooms[zoomLevel];
var parent = $('arc-event-bands');
......@@ -93,9 +93,9 @@ function setGraphicBuffersModel(model) {
406 /* kVsyncTimestamp */);
var cpusTitle = new EventBandTitle(parent, 'CPUs', 'arc-events-band-title');
var cpusBands = new CpuEventBands(
cpusTitle, 'arc-events-band', resolution, 0, model.duration);
cpusBands.setWidth(cpusBands.timestampToOffset(model.duration));
var cpusBands =
new CpuEventBands(cpusTitle, 'arc-events-band', resolution, 0, duration);
cpusBands.setWidth(cpusBands.timestampToOffset(duration));
cpusBands.setModel(model);
cpusBands.addChartToExistingArea(0 /* top */, cpusBands.height);
cpusBands.addChartSources(
......@@ -114,9 +114,9 @@ function setGraphicBuffersModel(model) {
var memoryTitle =
new EventBandTitle(parent, 'Memory', 'arc-events-band-title');
var memoryBands = new EventBands(
memoryTitle, 'arc-events-band', resolution, 0, model.duration);
memoryBands.setWidth(memoryBands.timestampToOffset(model.duration));
var memoryBands =
new EventBands(memoryTitle, 'arc-events-band', resolution, 0, duration);
memoryBands.setWidth(memoryBands.timestampToOffset(duration));
memoryBands.addChart(chartHeight, topBandPadding);
// Used memory chart.
memoryBands.addChartSources(
......@@ -146,9 +146,9 @@ function setGraphicBuffersModel(model) {
var chromeTitle =
new EventBandTitle(parent, 'Chrome graphics', 'arc-events-band-title');
var chromeBands = new EventBands(
chromeTitle, 'arc-events-band', resolution, 0, model.duration);
chromeBands.setWidth(chromeBands.timestampToOffset(model.duration));
var chromeBands =
new EventBands(chromeTitle, 'arc-events-band', resolution, 0, duration);
chromeBands.setWidth(chromeBands.timestampToOffset(duration));
for (var i = 0; i < model.chrome.buffers.length; i++) {
chromeBands.addBand(
new Events(model.chrome.buffers[i], 500, 599), topBandHeight,
......@@ -173,9 +173,9 @@ function setGraphicBuffersModel(model) {
var androidTitle =
new EventBandTitle(parent, 'Android graphics', 'arc-events-band-title');
var androidBands = new EventBands(
androidTitle, 'arc-events-band', resolution, 0, model.duration);
androidBands.setWidth(androidBands.timestampToOffset(model.duration));
var androidBands =
new EventBands(androidTitle, 'arc-events-band', resolution, 0, duration);
androidBands.setWidth(androidBands.timestampToOffset(duration));
androidBands.addBand(
new Events(model.android.buffers[0], 400, 499), topBandHeight,
topBandPadding);
......@@ -208,8 +208,8 @@ function setGraphicBuffersModel(model) {
var activityTitle = new EventBandTitle(
parent, activityTitleText, 'arc-events-band-title', icon);
var activityBands = new EventBands(
activityTitle, 'arc-events-band', resolution, 0, model.duration);
activityBands.setWidth(activityBands.timestampToOffset(model.duration));
activityTitle, 'arc-events-band', resolution, 0, duration);
activityBands.setWidth(activityBands.timestampToOffset(duration));
for (var j = 0; j < view.buffers.length; j++) {
// Android buffer events.
activityBands.addBand(
......@@ -245,9 +245,9 @@ function setGraphicBuffersModel(model) {
if (model.input && model.input.buffers.length > 0) {
var inputTitle =
new EventBandTitle(parent, 'Input', 'arc-events-band-title');
var inputBands = new EventBands(
inputTitle, 'arc-events-band', resolution, 0, model.duration);
inputBands.setWidth(inputBands.timestampToOffset(model.duration));
var inputBands =
new EventBands(inputTitle, 'arc-events-band', resolution, 0, duration);
inputBands.setWidth(inputBands.timestampToOffset(duration));
for (var i = 0; i < model.input.buffers.length; i++) {
inputBands.addBand(
new Events(model.input.buffers[i], 700, 799), topBandHeight,
......@@ -262,8 +262,8 @@ function setGraphicBuffersModel(model) {
var timeRulerTitle =
new EventBandTitle(parent, '' /* title */, 'arc-time-ruler-title');
var timeRulerBands = new EventBands(
timeRulerTitle, 'arc-events-band', resolution, 0, model.duration);
timeRulerBands.setWidth(timeRulerBands.timestampToOffset(model.duration));
timeRulerTitle, 'arc-events-band', resolution, 0, duration);
timeRulerBands.setWidth(timeRulerBands.timestampToOffset(duration));
// Reseve space for ticks and global events.
timeRulerBands.updateHeight(timeRulerEventHeight, 0 /* padding */);
......@@ -273,7 +273,7 @@ function setGraphicBuffersModel(model) {
var timeTick = 0;
var timeTickOffset = 20 * resolution;
var timeTickIndex = 0;
while (timeTick < model.duration) {
while (timeTick < duration) {
if ((timeTickIndex % 10) == 0) {
timeEvents.push([kTimeMark, timeTick]);
} else {
......@@ -303,7 +303,7 @@ function setGraphicBuffersModel(model) {
timeRulerBands.updateHeight(timeRulerLabelHeight, 0 /* padding */);
timeTick = 0;
timeTickOffset = 200 * resolution;
while (timeTick < model.duration) {
while (timeTick < duration) {
SVG.addText(
timeRulerBands.svg, timeRulerBands.timestampToOffset(timeTick),
timeRulerEventHeight, timeRulerBands.fontSize,
......
......@@ -98,9 +98,7 @@ function addModelHeader(model) {
new Date(model.information.timestamp).toLocaleString() :
'Unknown date';
header.getElementsByClassName('arc-tracing-app-date')[0].textContent = date;
var duration = model.information.duration ?
(model.information.duration * 0.000001).toFixed(2) :
'unknown duration';
var duration = (model.information.duration * 0.000001).toFixed(2);
header.getElementsByClassName('arc-tracing-app-duration')[0].textContent =
duration;
var platform = model.information.platform ? model.information.platform :
......@@ -108,14 +106,12 @@ function addModelHeader(model) {
header.getElementsByClassName('arc-tracing-app-platform')[0].textContent =
platform;
if (model.information.duration) {
header.getElementsByClassName('arc-tracing-app-fps')[0].textContent =
calculateFPS(getAppCommitEvents(model), model.information.duration)
.toFixed(2);
header.getElementsByClassName('arc-tracing-chrome-fps')[0].textContent =
calculateFPS(getChromeSwapEvents(model), model.information.duration)
.toFixed(2);
}
header.getElementsByClassName('arc-tracing-app-fps')[0].textContent =
calculateFPS(getAppCommitEvents(model), model.information.duration)
.toFixed(2);
header.getElementsByClassName('arc-tracing-chrome-fps')[0].textContent =
calculateFPS(getChromeSwapEvents(model), model.information.duration)
.toFixed(2);
var cpuPower = getAveragePower(model, 10 /* kCpuPower */);
var gpuPower = getAveragePower(model, 11 /* kGpuPower */);
......@@ -458,7 +454,7 @@ function refreshModels() {
var duration = 0;
for (i = 0; i < models.length; i++) {
duration = Math.max(duration, models[i].duration);
duration = Math.max(duration, models[i].information.duration);
}
for (i = 0; i < models.length; i++) {
......@@ -516,14 +512,6 @@ function setModelColor(model) {
* @param {object} model to add.
*/
function addModel(model) {
// Patch old models that do not have model.information to simplify checks.
if (!model.information) {
model.information = {};
if (!model.information.duration) {
model.information.duration = model.duration;
}
}
models.push(model);
setModelColor(model);
......
......@@ -5,10 +5,8 @@
#include "chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.h"
#include <map>
#include <string>
#include "ash/public/cpp/shell_window_ids.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
......@@ -50,13 +48,6 @@ namespace chromeos {
namespace {
constexpr char kKeyInformation[] = "information";
constexpr char kKeyIcon[] = "icon";
constexpr char kKeyTitle[] = "title";
constexpr char kKeyPlatform[] = "platform";
constexpr char kKeyTimestamp[] = "timestamp";
constexpr char kKeyDuration[] = "duration";
constexpr char kLastTracingModelName[] = "last_tracing_model.json";
enum class Action {
......@@ -200,7 +191,9 @@ void UpdateThreads(arc::ArcSystemModel::ThreadMap* threads) {
std::pair<base::Value, std::string> BuildGraphicsModel(
const std::string& data,
ArcGraphicsTracingMode mode,
base::DictionaryValue task_information,
const std::string& title,
const std::vector<unsigned char>& icon_png,
base::Time timestamp,
std::unique_ptr<arc::ArcSystemStatCollector> system_stat_collector,
const base::TimeTicks& time_min,
const base::TimeTicks& time_max,
......@@ -231,12 +224,11 @@ std::pair<base::Value, std::string> BuildGraphicsModel(
}
UpdateThreads(&graphics_model.system_model().thread_map());
graphics_model.set_app_title(title);
graphics_model.set_app_icon_png(icon_png);
graphics_model.set_platform(base::GetLinuxDistro());
graphics_model.set_timestamp(timestamp);
std::unique_ptr<base::DictionaryValue> model = graphics_model.Serialize();
task_information.SetKey(
kKeyDuration,
base::Value(static_cast<double>(graphics_model.duration())));
model->SetKey(kKeyInformation, std::move(task_information));
std::string json_content;
base::JSONWriter::WriteWithOptions(
......@@ -435,21 +427,14 @@ void ArcGraphicsTracingHandler::UpdateActiveArcWindowInfo() {
DCHECK(arc_active_window_);
active_task_title_ = base::UTF16ToASCII(arc_active_window_->GetTitle());
task_information_.SetKey(kKeyTitle, base::Value(active_task_title_));
active_task_icon_png_.clear();
const gfx::ImageSkia* app_icon =
arc_active_window_->GetProperty(aura::client::kAppIconKey);
if (app_icon) {
std::vector<unsigned char> png_data;
if (gfx::PNGCodec::EncodeBGRASkBitmap(
app_icon->GetRepresentation(1.0f).GetBitmap(),
false /* discard_transparency */, &png_data)) {
const std::string png_data_as_string(
reinterpret_cast<const char*>(&png_data[0]), png_data.size());
std::string icon_content;
base::Base64Encode(png_data_as_string, &icon_content);
task_information_.SetKey(kKeyIcon, base::Value(icon_content));
}
gfx::PNGCodec::EncodeBGRASkBitmap(
app_icon->GetRepresentation(1.0f).GetBitmap(),
false /* discard_transparency */, &active_task_icon_png_);
}
}
......@@ -529,10 +514,7 @@ void ArcGraphicsTracingHandler::SetStatus(const std::string& status) {
}
void ArcGraphicsTracingHandler::OnTracingStarted() {
task_information_.Clear();
task_information_.SetKey(kKeyPlatform, base::Value(base::GetLinuxDistro()));
task_information_.SetKey(kKeyTimestamp,
base::Value(base::Time::Now().ToJsTime()));
timestamp_ = base::Time::Now();
UpdateActiveArcWindowInfo();
......@@ -560,7 +542,7 @@ void ArcGraphicsTracingHandler::OnTracingStopped(
FROM_HERE,
{base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&BuildGraphicsModel, std::move(string_data), mode_,
std::move(task_information_),
active_task_title_, active_task_icon_png_, timestamp_,
std::move(system_stat_colletor_), tracing_time_min_,
tracing_time_max_, model_path),
base::BindOnce(&ArcGraphicsTracingHandler::OnGraphicsModelReady,
......
......@@ -5,9 +5,10 @@
#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_ARC_GRAPHICS_TRACING_ARC_GRAPHICS_TRACING_HANDLER_H_
#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_ARC_GRAPHICS_TRACING_ARC_GRAPHICS_TRACING_HANDLER_H_
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
......@@ -124,7 +125,6 @@ class ArcGraphicsTracingHandler : public content::WebUIMessageHandler,
// Task id and title for current ARC window.
int active_task_id_ = -1;
std::string active_task_title_;
// Used to detect janks for the currently active ARC++ window.
std::unique_ptr<arc::ArcGraphicsJankDetector> jank_detector_;
......@@ -133,7 +133,9 @@ class ArcGraphicsTracingHandler : public content::WebUIMessageHandler,
std::unique_ptr<arc::ArcSystemStatCollector> system_stat_colletor_;
// Information about active task, title and icon.
base::DictionaryValue task_information_;
std::string active_task_title_;
std::vector<unsigned char> active_task_icon_png_;
base::Time timestamp_;
base::WeakPtrFactory<ArcGraphicsTracingHandler> weak_ptr_factory_{this};
......
......@@ -5,7 +5,13 @@
"chrome": { "buffers": [ [ [ 500, 1667.0 ], [ 501, 1808.0 ] ] ],
"global_events": [ ] },
"system": { "threads": {}, "cpu": [], "memory": []},
"duration": 1053800.0,
"information": {
"duration": 1000.0,
"icon": "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", "platform": "CrOS 12642.0.0 (Official Build) dev-channel eve-arcvm test",
"platform": "CrOS 12642.0.0 (Official Build) dev-channel eve",
"timestamp": 1.572898642036e+12,
"title": "Play Store"
},
"views": [ {
"activity": "com.android.vending/com.android.vending.AssetBrowserActivity",
"buffers": [ [ [ 200, 0.0 ], [ 201, 149.0 ] ], [ [ 300, 168.0 ], [ 202, 173.0 ] ] ],
......
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