Commit 666011d6 authored by Alexander Alekseev's avatar Alexander Alekseev Committed by Commit Bot

Ash HUD: Add simple FPS graph.

This CL adds a graph of the scanout time per seconds
reported in the PresentationFeedback.


Screenshot: https://screenshot.googleplex.com/7wZhCV78vtPskdu.png

Bug: 1075612
Change-Id: I16a5320532f35bdc3be4baa6e2bb59bd3db632b8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2463872
Commit-Queue: Alexander Alekseev <alemate@chromium.org>
Reviewed-by: default avatarkylechar <kylechar@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#820879}
parent c4dce80e
......@@ -490,12 +490,16 @@ component("ash") {
"host/root_window_transformer.h",
"host/transformer_helper.cc",
"host/transformer_helper.h",
"hud_display/compositor_stats.cc",
"hud_display/compositor_stats.h",
"hud_display/cpu_graph_page_view.cc",
"hud_display/cpu_graph_page_view.h",
"hud_display/cpu_status.cc",
"hud_display/cpu_status.h",
"hud_display/data_source.cc",
"hud_display/data_source.h",
"hud_display/fps_graph_page_view.cc",
"hud_display/fps_graph_page_view.h",
"hud_display/graph.cc",
"hud_display/graph.h",
"hud_display/graph_page_view_base.cc",
......
// Copyright 2020 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 "ash/hud_display/compositor_stats.h"
#include "ui/compositor/compositor.h"
#include "ui/gfx/presentation_feedback.h"
namespace ash {
namespace hud_display {
CompositorStats::CompositorStats(Observer* observer, ui::Compositor* compositor)
: observer_(observer), compositor_(compositor) {
compositor_->AddObserver(this);
}
CompositorStats::~CompositorStats() {
compositor_->RemoveObserver(this);
}
void CompositorStats::OnDidPresentCompositorFrame(
uint32_t frame_token,
const gfx::PresentationFeedback& feedback) {
constexpr base::TimeDelta kOneSec = base::TimeDelta::FromSeconds(1);
constexpr base::TimeDelta k500ms = base::TimeDelta::FromMilliseconds(500);
if (!feedback.failed())
presented_times_.push_back(feedback.timestamp);
const base::TimeTicks deadline_1s = feedback.timestamp - kOneSec;
while (!presented_times_.empty() && presented_times_.front() <= deadline_1s)
presented_times_.pop_front();
const float frame_rate_1s = presented_times_.size();
const base::TimeTicks deadline_500ms = feedback.timestamp - k500ms;
float frame_rate_500ms = 0;
for (auto i = presented_times_.crbegin();
(i != presented_times_.crend()) && (*i > deadline_500ms); ++i) {
++frame_rate_500ms;
}
frame_rate_500ms *= 2; // per second
observer_->OnFramePresented(frame_rate_1s, frame_rate_500ms,
compositor_->refresh_rate());
}
} // namespace hud_display
} // namespace ash
// Copyright 2020 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 ASH_HUD_DISPLAY_COMPOSITOR_STATS_H_
#define ASH_HUD_DISPLAY_COMPOSITOR_STATS_H_
#include "base/containers/circular_deque.h"
#include "base/time/time.h"
#include "ui/compositor/compositor_observer.h"
namespace ui {
class Compositor;
}
namespace gfx {
struct PresentationFeedback;
}
namespace ash {
namespace hud_display {
class CompositorStats : public ui::CompositorObserver {
public:
class Observer {
public:
Observer() = default;
virtual ~Observer() = default;
virtual void OnFramePresented(float frame_rate_1s,
float frame_rate_500ms,
float refresh_rate) = 0;
};
CompositorStats(Observer* observer, ui::Compositor* compositor);
~CompositorStats() override;
// ui::CompositorObserver:
void OnDidPresentCompositorFrame(
uint32_t frame_token,
const gfx::PresentationFeedback& feedback) override;
private:
Observer* const observer_;
ui::Compositor* const compositor_;
// |timestamp| from PresentationFeedback for one second.
base::circular_deque<base::TimeTicks> presented_times_;
};
} // namespace hud_display
} // namespace ash
#endif // ASH_HUD_DISPLAY_COMPOSITOR_STATS_H_
......@@ -23,27 +23,41 @@ BEGIN_METADATA(CpuGraphPageView, GraphPageViewBase)
END_METADATA
CpuGraphPageView::CpuGraphPageView(const base::TimeDelta refresh_interval)
: cpu_other_(Graph::Baseline::BASELINE_BOTTOM,
: cpu_other_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::SOLID,
Graph::Style::LINES,
SkColorSetA(SK_ColorMAGENTA, kHUDAlpha)),
cpu_system_(Graph::Baseline::BASELINE_BOTTOM,
cpu_system_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::SOLID,
Graph::Style::LINES,
SkColorSetA(SK_ColorRED, kHUDAlpha)),
cpu_user_(Graph::Baseline::BASELINE_BOTTOM,
cpu_user_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::SOLID,
Graph::Style::LINES,
SkColorSetA(SK_ColorBLUE, kHUDAlpha)),
cpu_idle_(Graph::Baseline::BASELINE_BOTTOM,
cpu_idle_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::SOLID,
Graph::Style::LINES,
SkColorSetA(SK_ColorDKGRAY, kHUDAlpha)) {
const int data_width = cpu_other_.GetDataBufferSize();
const int data_width = cpu_other_.max_data_points();
// Verical ticks are drawn every 10% (10/100 interval).
constexpr float vertical_ticks_interval = (10 / 100.0);
// -XX seconds on the left, 100% top, 0 seconds on the right, 0% on the
// bottom. Seconds and Gigabytes are dimentions. Number of data points is
// bottom. Seconds and Gigabytes are dimensions. Number of data points is
// cpu_other_.GetDataBufferSize(), horizontal grid ticks are drawn every 10
// seconds.
CreateGrid(
/*left=*/static_cast<int>(-data_width * refresh_interval.InSecondsF()),
/*top=*/100, /*right=*/0, /*bottom=*/0, base::ASCIIToUTF16("s"),
base::ASCIIToUTF16("%"), data_width, 10 / refresh_interval.InSecondsF());
/*top=*/100, /*right=*/0, /*bottom=*/0,
/*x_unit=*/base::ASCIIToUTF16("s"),
/*y_unit=*/base::ASCIIToUTF16("%"),
/*horizontal_points_number=*/data_width,
/*horizontal_ticks_interval=*/10 / refresh_interval.InSecondsF(),
vertical_ticks_interval);
Legend::Formatter formatter = base::BindRepeating([](float value) {
return base::ASCIIToUTF16(base::StringPrintf(
......
// Copyright 2020 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 "ash/hud_display/fps_graph_page_view.h"
#include <algorithm>
#include <cmath>
#include <numeric>
#include "ash/hud_display/grid.h"
#include "ash/hud_display/hud_constants.h"
#include "ash/shell.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/aura/window_tree_host.h"
#include "ui/gfx/canvas.h"
#include "ui/views/metadata/metadata_impl_macros.h"
namespace ash {
namespace hud_display {
namespace {
// Draw tick on the vertical axis every 5 frames.
constexpr float kVerticalTicFrames = 5;
} // namespace
////////////////////////////////////////////////////////////////////////////////
// FPSGraphPageView, public:
BEGIN_METADATA(FPSGraphPageView, GraphPageViewBase)
END_METADATA
FPSGraphPageView::FPSGraphPageView(const base::TimeDelta refresh_interval)
: frame_rate_1s_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::NONE,
Graph::Style::SKYLINE,
SkColorSetA(SK_ColorYELLOW, kHUDAlpha)),
frame_rate_500ms_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::NONE,
Graph::Style::SKYLINE,
SkColorSetA(SK_ColorCYAN, kHUDAlpha)),
refresh_rate_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::NONE,
Graph::Style::SKYLINE,
kHUDBackground /*not drawn*/),
compositor_stats_(
this,
Shell::Get()->GetAllRootWindows().front()->GetHost()->compositor()) {
const int data_width = frame_rate_1s_.max_data_points();
// Verical ticks are drawn every 5 frames (5/60 interval).
constexpr float vertical_ticks_interval = kVerticalTicFrames / 60.0;
// max_data_points left label, 60fps top, 0 seconds on the right, 0fps on the
// bottom. Seconds and fps are dimensions. Number of data points is
// data_width, horizontal grid ticks are drawn every 10 frames.
grid_ = CreateGrid(
/*left=*/data_width,
/*top=*/60, /*right=*/0, /*bottom=*/0,
/*x_unit=*/base::ASCIIToUTF16("frames"),
/*y_unit=*/base::ASCIIToUTF16("fps"),
/*horizontal_points_number=*/data_width,
/*horizontal_ticks_interval=*/10, vertical_ticks_interval);
Legend::Formatter formatter_float = base::BindRepeating([](float value) {
return base::ASCIIToUTF16(base::StringPrintf("%.1f", value));
});
Legend::Formatter formatter_int = base::BindRepeating([](float value) {
return base::ASCIIToUTF16(base::StringPrintf("%d", (int)value));
});
const std::vector<Legend::Entry> legend(
{{refresh_rate_, base::ASCIIToUTF16("Refresh rate"),
base::ASCIIToUTF16("Actual display refresh rate."), formatter_int},
{frame_rate_1s_, base::ASCIIToUTF16("1s FPS"),
base::ASCIIToUTF16(
"Number of frames successfully presented per 1 second."),
formatter_float},
{frame_rate_500ms_, base::ASCIIToUTF16(".5s FPS"),
base::ASCIIToUTF16("Number of frames successfully presented per 0.5 "
"second scaled to a second."),
formatter_float}});
CreateLegend(legend);
}
FPSGraphPageView::~FPSGraphPageView() = default;
////////////////////////////////////////////////////////////////////////////////
void FPSGraphPageView::OnPaint(gfx::Canvas* canvas) {
// TODO: Should probably update last graph point more often than shift graph.
// Layout graphs.
gfx::Rect rect = GetContentsBounds();
// Adjust to grid width.
rect.Inset(kGridLineWidth, kGridLineWidth);
frame_rate_500ms_.Layout(rect, /*base=*/nullptr);
frame_rate_1s_.Layout(rect, /*base=*/nullptr);
frame_rate_500ms_.Draw(canvas);
frame_rate_1s_.Draw(canvas);
// Refresh rate graph is not drawn, it's just used in Legend display and
// grid calculations.
}
void FPSGraphPageView::OnFramePresented(float frame_rate_1s,
float frame_rate_500ms,
float refresh_rate) {
UpdateTopLabel(refresh_rate);
frame_rate_1s_.AddValue(frame_rate_1s / grid_->top_label(), frame_rate_1s);
frame_rate_500ms_.AddValue(frame_rate_500ms / grid_->top_label(),
frame_rate_500ms);
const float max_refresh_rate =
std::max(refresh_rate, refresh_rate_.GetUnscaledValueAt(0));
refresh_rate_.AddValue(max_refresh_rate / grid_->top_label(),
max_refresh_rate);
// Legend update is expensive. Do it synchronously on regular intervals only.
SchedulePaint();
}
void FPSGraphPageView::UpdateData(const DataSource::Snapshot& snapshot) {
// Graph moves only on FramePresented.
// Update legend only.
RefreshLegendValues();
}
void FPSGraphPageView::UpdateTopLabel(float refresh_rate) {
if (grid_->top_label() < unsigned(refresh_rate)) {
const unsigned refresh_rate_rounded_10 =
ceilf(unsigned(refresh_rate) / 10.0F) * 10;
grid_->SetTopLabel(refresh_rate_rounded_10);
grid_->SetVerticalTicsInterval(kVerticalTicFrames /
refresh_rate_rounded_10);
}
}
} // namespace hud_display
} // namespace ash
// Copyright 2020 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 ASH_HUD_DISPLAY_FPS_GRAPH_PAGE_VIEW_H_
#define ASH_HUD_DISPLAY_FPS_GRAPH_PAGE_VIEW_H_
#include "time.h"
#include "ash/hud_display/compositor_stats.h"
#include "ash/hud_display/graph.h"
#include "ash/hud_display/graph_page_view_base.h"
namespace ash {
namespace hud_display {
class Grid;
// Draws FPS graphs.
// Graph is updated in two ways:
// 1. Regular update with UpdateData() sifts graph and adds new point.
// 2. Every time OnFramePresented() is called, the last graph value is updated.
class FPSGraphPageView : public GraphPageViewBase,
public CompositorStats::Observer {
public:
METADATA_HEADER(FPSGraphPageView);
explicit FPSGraphPageView(const base::TimeDelta refresh_interval);
FPSGraphPageView(const FPSGraphPageView&) = delete;
FPSGraphPageView& operator=(const FPSGraphPageView&) = delete;
~FPSGraphPageView() override;
// views::View
void OnPaint(gfx::Canvas* canvas) override;
// CompositorStats::Observer
void OnFramePresented(float frame_rate_1s,
float frame_rate_500ms,
float refresh_rate) override;
// Update page data from the new snapshot.
void UpdateData(const DataSource::Snapshot& snapshot) override;
private:
// Sets grid top label to the maximum of the observed refresh rate.
void UpdateTopLabel(float f_refresh_rate);
// Number of frames per second presented.
Graph frame_rate_1s_;
Graph frame_rate_500ms_;
// Active display refresh rate.
Graph refresh_rate_;
Grid* grid_; // not owned
CompositorStats compositor_stats_;
};
} // namespace hud_display
} // namespace ash
#endif // ASH_HUD_DISPLAY_FPS_GRAPH_PAGE_VIEW_H_
......@@ -17,8 +17,15 @@
namespace ash {
namespace hud_display {
Graph::Graph(Baseline baseline, Fill fill, SkColor color)
: baseline_(baseline), fill_(fill), color_(color) {}
Graph::Graph(size_t max_data_points,
Baseline baseline,
Fill fill,
Style style,
SkColor color)
: baseline_(baseline), fill_(fill), style_(style), color_(color) {
DCHECK_LT(max_data_points, data_.BufferSize());
max_data_points_ = std::min(max_data_points, data_.BufferSize() - 1);
}
Graph::~Graph() {}
......@@ -30,8 +37,7 @@ void Graph::AddValue(float value, float unscaled_value) {
void Graph::Layout(const gfx::Rect& graph_bounds, const Graph* base) {
graph_bounds_ = graph_bounds;
// base::RingBuffer::Iterator::index never reaches 0. Thus "-1";
const float scale_x = graph_bounds.width() / (data_.BufferSize() - 1.f);
const float scale_x = graph_bounds.width() / (float)max_data_points_;
// Assume data is already scaled to [0-1], which will map to full
// |graph_bounds| height.
......@@ -46,18 +52,19 @@ void Graph::Layout(const gfx::Rect& graph_bounds, const Graph* base) {
bottom_path_.resize(0);
if (base) {
bottom_path_.reserve(base->top_path().size());
bottom_path_style_ = base->style_;
for (const SkPoint& point : base->top_path())
bottom_path_.push_back({point.x(), point.y() + pixel_adjust});
}
top_path_.resize(0);
top_path_.reserve(data_.BufferSize());
top_path_.reserve(max_data_points_);
size_t i = 0;
// base::RingBuffer<> does not conform to C++ containers specification, so
// it's End() is actually Back().
for (Graph::Data::Iterator it = data_.End(); it; --it) {
const float value = **it;
float x = graph_bounds.x() + it.index() * scale_x;
float x = graph_bounds.x() + (max_data_points_ - i) * scale_x;
float y =
(baseline_ == Baseline::BASELINE_BOTTOM ? -1 : 1) * value * scale_y;
if (bottom_path_.size()) {
......@@ -71,6 +78,8 @@ void Graph::Layout(const gfx::Rect& graph_bounds, const Graph* base) {
}
top_path_.push_back({x, y});
++i;
if (i >= max_data_points_)
break;
}
// This is the first layer from the start and it is filled and is non-empty.
......@@ -91,19 +100,64 @@ void Graph::Draw(gfx::Canvas* canvas) const {
SkPath path;
path.moveTo(top_path_.front());
CHECK(top_path_.size());
for (std::vector<SkPoint>::const_iterator it = top_path_.begin();
it != top_path_.end(); ++it) {
if (it == top_path_.begin())
continue;
path.lineTo(*it);
const auto draw_top_line = [](const std::vector<SkPoint>& top_path,
const auto& draw_point, SkPath& out_path) {
SkPoint previous_point = top_path.front();
for (std::vector<SkPoint>::const_iterator it = top_path.begin();
it != top_path.end(); ++it) {
// For the top line we are already here.
if (it == top_path.begin())
continue;
// Depending on the line type, |draw_point| may use the previous point.
draw_point(*it, previous_point, out_path);
}
if (fill_ == Graph::Fill::SOLID) {
};
const auto draw_bottom_line = [](const std::vector<SkPoint>& bottom_path,
const auto& draw_point, SkPath& result) {
SkPoint previous_point = bottom_path.back();
for (std::vector<SkPoint>::const_reverse_iterator it =
bottom_path_.crbegin();
it != bottom_path_.crend(); ++it) {
path.lineTo(*it);
bottom_path.crbegin();
it != bottom_path.crend(); ++it) {
// Bottom line needs line to the first point too.
// Depending on the line type, |draw_point| may use the previous point.
draw_point(*it, previous_point, result);
}
};
// This is used to draw both top and bottom Style::LINES paths.
const auto draw_lines_point =
[](const SkPoint& point, const SkPoint& /*previous_point*/,
SkPath& out_path) { out_path.lineTo(point); };
// Top and bottom Style::SKYLINE drawing functions are symmetric.
const auto draw_skyline_point =
[](const SkPoint& point, SkPoint& previous_point, SkPath& out_path) {
out_path.lineTo(SkPoint::Make(point.x(), previous_point.y()));
out_path.lineTo(point);
previous_point = point;
};
const auto draw_bottom_skyline_point =
[](const SkPoint& point, SkPoint& previous_point, SkPath& out_path) {
out_path.lineTo(SkPoint::Make(previous_point.x(), point.y()));
out_path.lineTo(point);
previous_point = point;
};
switch (style_) {
case Style::LINES:
draw_top_line(top_path_, draw_lines_point, path);
break;
case Style::SKYLINE:
draw_top_line(top_path_, draw_skyline_point, path);
break;
}
if (fill_ == Graph::Fill::SOLID) {
switch (bottom_path_style_) {
case Style::LINES:
draw_bottom_line(bottom_path_, draw_lines_point, path);
break;
case Style::SKYLINE:
draw_bottom_line(bottom_path_, draw_bottom_skyline_point, path);
break;
}
}
cc::PaintFlags flags;
......@@ -118,7 +172,19 @@ void Graph::Draw(gfx::Canvas* canvas) const {
canvas->DrawPath(path, flags);
}
void Graph::UpdateLastValue(float value, float unscaled_value) {
const size_t last_index = data_.BufferSize() - 1;
if (!data_.IsFilledIndex(last_index))
return;
*data_.MutableReadBuffer(last_index) = value;
*unscaled_data_.MutableReadBuffer(last_index) = unscaled_value;
}
float Graph::GetUnscaledValueAt(size_t index) const {
if (index >= max_data_points_)
return 0;
// 0 - the oldest value
// BufferSize() - 1 - the newest value.
const size_t raw_index = index < unscaled_data_.BufferSize()
......@@ -133,6 +199,9 @@ float Graph::GetUnscaledValueAt(size_t index) const {
}
bool Graph::IsFilledIndex(size_t index) const {
if (index >= max_data_points_)
return false;
// 0 - the oldest value
// BufferSize() - 1 - the newest value.
const size_t raw_index =
......@@ -156,7 +225,7 @@ std::string Graph::DebugDump(const std::string& name) const {
if (fill_ == Graph::Fill::SOLID) {
// Print filled graph as a set of vertical lines.
if (top_path_.size() == bottom_path_.size()) {
// Each point op the top has matching point on the bottom.
// Each point on the top has matching point on the bottom.
os << "\t" << name << ": " << i << ": [" << bottom_path_[i].x() << ", "
<< bottom_path_[i].y() << "] -> [" << top_path_[i].x() << ", "
<< top_path_[i].y() << "]";
......@@ -173,6 +242,7 @@ std::string Graph::DebugDump(const std::string& name) const {
os << "\t" << name << ": " << i << ": -> [" << top_path_[i].x() << ", "
<< top_path_[i].y() << "]";
}
os << "\n";
}
return os.str();
}
......
......@@ -39,7 +39,17 @@ class Graph {
SOLID,
};
Graph(Baseline baseline, Fill fill, SkColor color);
enum class Style {
LINES,
SKYLINE,
};
// |max_data_points| must be less than the ring buffer size.
Graph(size_t max_data_points,
Baseline baseline,
Fill fill,
Style style,
SkColor color);
~Graph();
Graph(const Graph&) = delete;
......@@ -51,10 +61,12 @@ class Graph {
void AddValue(float value, float unscaled_value);
void Layout(const gfx::Rect& graph_bounds, const Graph* base);
void Draw(gfx::Canvas* canvas) const;
void UpdateLastValue(float value, float unscaled_value);
const std::vector<SkPoint>& top_path() const { return top_path_; }
size_t GetDataBufferSize() const { return data_.BufferSize(); }
// Returns number of data points displayed on the graph.
size_t max_data_points() const { return max_data_points_; }
SkColor color() const { return color_; }
......@@ -78,6 +90,7 @@ class Graph {
private:
const Baseline baseline_;
const Fill fill_;
const Style style_;
const SkColor color_;
// Result of last Layout() call.
......@@ -90,9 +103,12 @@ class Graph {
// Paths are calculated by Layout() from the |data_|.
std::vector<SkPoint> top_path_;
std::vector<SkPoint> bottom_path_;
// Bottom path style should follow base graph style.
Style bottom_path_style_ = Style::LINES;
Data data_;
Data unscaled_data_;
size_t max_data_points_ = 0;
};
} // namespace hud_display
......
......@@ -149,11 +149,12 @@ Grid* GraphPageViewBase::CreateGrid(float left,
const base::string16& x_unit,
const base::string16& y_unit,
int horizontal_points_number,
int horizontal_ticks_interval) {
int horizontal_ticks_interval,
float vertical_ticks_interval) {
DCHECK(grid_container_->children().empty());
return grid_container_->AddChildView(std::make_unique<Grid>(
left, top, right, bottom, x_unit, y_unit, horizontal_points_number,
horizontal_ticks_interval));
horizontal_ticks_interval, vertical_ticks_interval));
}
void GraphPageViewBase::RefreshLegendValues() {
......
......@@ -36,7 +36,7 @@ class GraphPageViewBase : public views::View {
// Adds default legend.
void CreateLegend(const std::vector<Legend::Entry>& entries);
// Put grid in its dedicated container.
// Put grid in its dedicated container. See Grid class for details.
Grid* CreateGrid(float left,
float top,
float right,
......@@ -44,7 +44,8 @@ class GraphPageViewBase : public views::View {
const base::string16& x_unit,
const base::string16& y_unit,
int horizontal_points_number,
int horizontal_ticks_interval);
int horizontal_ticks_interval,
float vertical_ticks_interval);
protected:
void RefreshLegendValues();
......
......@@ -7,6 +7,7 @@
#include <numeric>
#include "ash/hud_display/cpu_graph_page_view.h"
#include "ash/hud_display/fps_graph_page_view.h"
#include "ash/hud_display/hud_constants.h"
#include "ash/hud_display/memory_graph_page_view.h"
#include "base/bind.h"
......@@ -20,7 +21,7 @@ namespace {
// UI refresh interval.
constexpr base::TimeDelta kGraphsDataRefreshInterval =
base::TimeDelta::FromMilliseconds(500);
base::TimeDelta::FromMilliseconds(1000);
} // namespace
......@@ -47,6 +48,9 @@ GraphsContainerView::GraphsContainerView() {
AddChildView(
std::make_unique<CpuGraphPageView>(refresh_timer_.GetCurrentDelay()))
->SetID(static_cast<int>(DisplayMode::CPU_DISPLAY));
AddChildView(
std::make_unique<FPSGraphPageView>(refresh_timer_.GetCurrentDelay()))
->SetID(static_cast<int>(DisplayMode::FPS_DISPLAY));
}
GraphsContainerView::~GraphsContainerView() {
......
......@@ -46,7 +46,8 @@ Grid::Grid(float left,
const base::string16& x_unit,
const base::string16& y_unit,
int horizontal_points_number,
int horizontal_ticks_interval)
int horizontal_ticks_interval,
float vertical_ticks_interval)
: color_(kGridColor),
left_(left),
top_(top),
......@@ -55,7 +56,8 @@ Grid::Grid(float left,
x_unit_(x_unit),
y_unit_(y_unit),
horizontal_points_number_(horizontal_points_number),
horizontal_ticks_interval_(horizontal_ticks_interval) {
horizontal_ticks_interval_(horizontal_ticks_interval),
vertical_ticks_interval_(vertical_ticks_interval) {
// not implemented.
ALLOW_UNUSED_LOCAL(right_);
......@@ -147,19 +149,21 @@ void Grid::OnPaint(gfx::Canvas* canvas) {
const SkScalar tick_length = 3;
// Vertical interval ticks (drawn horizontally).
constexpr int v_ticks = 10; // Draw 10 vertical intervals between 0 and 100%.
for (int i = 1; i < v_ticks; ++i) {
if (vertical_ticks_interval_ > 0) {
float tick_bottom_offset = vertical_ticks_interval_;
while (tick_bottom_offset <= 1) {
// Skip 50%.
if (i == v_ticks / 2)
continue;
const SkScalar line_y = bounds().height() / (float)v_ticks * i;
if (fabs(tick_bottom_offset - .5) > 0.01) {
const SkScalar line_y = (1 - tick_bottom_offset) * bounds().height();
solid_path.moveTo({0, line_y});
solid_path.lineTo({tick_length, line_y});
solid_path.moveTo({bounds().width() - tick_length, line_y});
solid_path.lineTo({bounds().width(), line_y});
}
tick_bottom_offset += vertical_ticks_interval_;
}
}
// Horizontal interval ticks (drawn vertically).
if (horizontal_points_number_ > 0 && horizontal_ticks_interval_ > 0) {
......@@ -223,5 +227,13 @@ void Grid::SetLeftLabel(float left) {
InvalidateLayout();
}
void Grid::SetVerticalTicsInterval(float interval) {
interval == std::abs(interval) >= 1 ? 0 : std::abs(interval);
if (interval == vertical_ticks_interval_)
return;
vertical_ticks_interval_ = interval;
}
} // namespace hud_display
} // namespace ash
......@@ -31,6 +31,7 @@ class Grid : public views::View {
// To draw horizontal ticks, graph data is assumed to have
// |horizontal_points_number| points horizontally along the full graph width,
// and ticks will be drawn every |horizontal_ticks_interval| from the right.
// |vertical_ticks_interval| must be in between [0,1] (as grid data values).
Grid(float left,
float top,
float right,
......@@ -38,7 +39,8 @@ class Grid : public views::View {
const base::string16& x_unit,
const base::string16& y_unit,
int horizontal_points_number,
int horizontal_ticks_interval);
int horizontal_ticks_interval,
float vertical_ticks_interval);
Grid(const Grid&) = delete;
Grid& operator=(const Grid&) = delete;
......@@ -53,6 +55,9 @@ class Grid : public views::View {
void SetTopLabel(float top);
void SetBottomLabel(float bottom);
void SetLeftLabel(float left);
void SetVerticalTicsInterval(float interval);
float top_label() const { return top_; }
private:
const SkColor color_;
......@@ -69,6 +74,7 @@ class Grid : public views::View {
// horizontal ticks
int horizontal_points_number_ = 0;
int horizontal_ticks_interval_ = 0;
float vertical_ticks_interval_ = 0;
// Graph labels
views::Label* right_top_label_ = nullptr; // not owned
......
......@@ -46,6 +46,7 @@ enum class DisplayMode {
CPU_DISPLAY =
1, // First value should be different from default Views::ID = 0.
MEMORY_DISPLAY,
FPS_DISPLAY,
};
} // namespace hud_display
......
......@@ -134,11 +134,12 @@ HUDDisplayView::HUDDisplayView() {
// Setup header.
// TODO: Add tab buttons via:
header_view_->tab_strip()->AddTabButton(DisplayMode::CPU_DISPLAY,
base::ASCIIToUTF16("CPU"));
header_view_->tab_strip()->AddTabButton(DisplayMode::MEMORY_DISPLAY,
base::ASCIIToUTF16("RAM"));
header_view_->tab_strip()->AddTabButton(DisplayMode::FPS_DISPLAY,
base::ASCIIToUTF16("FPS"));
// Setup data.
data->SetBackground(views::CreateSolidBackground(kHUDBackground));
......@@ -162,19 +163,6 @@ HUDDisplayView::~HUDDisplayView() {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
}
int HUDDisplayView::NonClientHitTest(const gfx::Point& point) {
const View* view = GetEventHandlerForPoint(point);
if (!view)
return HTNOWHERE;
return view->GetProperty(kHUDClickHandler);
}
void HUDDisplayView::SetDisplayMode(DisplayMode display_mode) {
graphs_container_->SetMode(display_mode);
header_view_->tab_strip()->ActivateTab(display_mode);
}
views::ClientView* HUDDisplayView::CreateClientView(views::Widget* widget) {
return new HTClientView(this, widget, TransferOwnershipOfContentsView());
}
......@@ -194,5 +182,18 @@ void HUDDisplayView::OnSettingsToggle() {
graphs_container_->SetVisible(!settings_view_->GetVisible());
}
int HUDDisplayView::NonClientHitTest(const gfx::Point& point) {
const View* view = GetEventHandlerForPoint(point);
if (!view)
return HTNOWHERE;
return view->GetProperty(kHUDClickHandler);
}
void HUDDisplayView::SetDisplayMode(DisplayMode display_mode) {
graphs_container_->SetMode(display_mode);
header_view_->tab_strip()->ActivateTab(display_mode);
}
} // namespace hud_display
} // namespace ash
......@@ -40,6 +40,9 @@ class LegendEntry : public views::View {
void SetValueIndex(size_t index);
void RefreshValue();
// This is used by parent to match sizes.
views::View* value() { return value_; }
private:
const SkColor color_;
const Graph& graph_;
......@@ -170,6 +173,30 @@ Legend::Legend(const std::vector<Legend::Entry>& contents) {
Legend::~Legend() = default;
void Legend::Layout() {
views::View::Layout();
gfx::Size max_size;
bool updated = false;
for (auto* view : children()) {
if (view->GetClassName() != LegendEntry::kViewClassName)
continue;
views::View* value = static_cast<LegendEntry*>(view)->value();
max_size.SetToMax(value->GetPreferredSize());
updated |= max_size != value->GetPreferredSize();
}
if (updated) {
for (auto* view : children()) {
if (view->GetClassName() != LegendEntry::kViewClassName)
continue;
static_cast<LegendEntry*>(view)->value()->SetPreferredSize(max_size);
}
views::View::Layout();
}
}
void Legend::SetValuesIndex(size_t index) {
for (auto* view : children()) {
if (view->GetClassName() != LegendEntry::kViewClassName)
......
......@@ -45,6 +45,9 @@ class Legend : public views::View {
~Legend() override;
// views::View:
void Layout() override;
// Display values for the given index. |index| is always interpreted as
// "negative", i.e. "0" - current data, "1" - previous graph data, 2 - two
// steps ago. I.e. it's number of graph points from the right graph edge.
......
......@@ -24,39 +24,60 @@ BEGIN_METADATA(MemoryGraphPageView, GraphPageViewBase)
END_METADATA
MemoryGraphPageView::MemoryGraphPageView(const base::TimeDelta refresh_interval)
: graph_chrome_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
: graph_chrome_rss_private_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::SOLID,
Graph::Style::LINES,
SkColorSetA(SK_ColorRED, kHUDAlpha)),
graph_mem_free_(Graph::Baseline::BASELINE_BOTTOM,
graph_mem_free_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::NONE,
Graph::Style::LINES,
SkColorSetA(SK_ColorDKGRAY, kHUDAlpha)),
graph_mem_used_unknown_(Graph::Baseline::BASELINE_BOTTOM,
graph_mem_used_unknown_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::SOLID,
Graph::Style::LINES,
SkColorSetA(SK_ColorLTGRAY, kHUDAlpha)),
graph_renderers_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
graph_renderers_rss_private_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::SOLID,
Graph::Style::LINES,
SkColorSetA(SK_ColorCYAN, kHUDAlpha)),
graph_arc_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
graph_arc_rss_private_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::SOLID,
Graph::Style::LINES,
SkColorSetA(SK_ColorMAGENTA, kHUDAlpha)),
graph_gpu_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
graph_gpu_rss_private_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::SOLID,
Graph::Style::LINES,
SkColorSetA(SK_ColorRED, kHUDAlpha)),
graph_gpu_kernel_(Graph::Baseline::BASELINE_BOTTOM,
graph_gpu_kernel_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::SOLID,
Graph::Style::LINES,
SkColorSetA(SK_ColorYELLOW, kHUDAlpha)),
graph_chrome_rss_shared_(Graph::Baseline::BASELINE_BOTTOM,
graph_chrome_rss_shared_(kDefaultGraphWidth,
Graph::Baseline::BASELINE_BOTTOM,
Graph::Fill::NONE,
Graph::Style::LINES,
SkColorSetA(SK_ColorBLUE, kHUDAlpha)) {
const int data_width = graph_arc_rss_private_.GetDataBufferSize();
const int data_width = graph_arc_rss_private_.max_data_points();
// Verical ticks are drawn every 10% (10/100 interval).
constexpr float vertical_ticks_interval = 10 / 100.0;
// -XX seconds on the left, 0Gb top (will be updated later), 0 seconds on the
// right, 0 Gb on the bottom. Seconds and Gigabytes are dimentions. Number of
// right, 0 Gb on the bottom. Seconds and Gigabytes are dimensions. Number of
// data points is data_width. horizontal grid ticks are drawn every 10
// seconds.
grid_ = CreateGrid(
static_cast<int>(/*left=*/-data_width * refresh_interval.InSecondsF()),
/*top=*/0, /*right=*/0, /*bottom=*/0, base::ASCIIToUTF16("s"),
base::ASCIIToUTF16("Gb"), data_width, 10 / refresh_interval.InSecondsF());
/*top=*/0, /*right=*/0, /*bottom=*/0, /*x_unit=*/base::ASCIIToUTF16("s"),
/*y_unit=*/base::ASCIIToUTF16("Gb"),
/*horizontal_points_number=*/data_width,
/*horizontal_ticks_interval=*/10 / refresh_interval.InSecondsF(),
vertical_ticks_interval);
// Hide grid until we know total memory size.
grid_->SetVisible(false);
......
......@@ -689,6 +689,8 @@ void Compositor::DidPresentCompositorFrame(
TRACE_EVENT_MARK_WITH_TIMESTAMP1("cc,benchmark", "FramePresented",
feedback.timestamp, "environment",
"browser");
for (auto& observer : observer_list_)
observer.OnDidPresentCompositorFrame(frame_token, feedback);
}
void Compositor::DidSubmitCompositorFrame() {
......
......@@ -11,7 +11,8 @@
namespace gfx {
class Size;
}
struct PresentationFeedback;
} // namespace gfx
namespace ui {
......@@ -51,6 +52,11 @@ class COMPOSITOR_EXPORT CompositorObserver {
// Called at the top of the compositor's destructor, to give observers a
// chance to remove themselves.
virtual void OnCompositingShuttingDown(Compositor* compositor) {}
// Called when the presentation feedback was received from the viz.
virtual void OnDidPresentCompositorFrame(
uint32_t frame_token,
const gfx::PresentationFeedback& feedback) {}
};
} // namespace ui
......
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