Skip to content
This repository was archived by the owner on Jan 31, 2025. It is now read-only.

Commit 074e46f

Browse files
Make MultivariateTimeSeries a non-template class (#4723)
* Make MultivariateTimeSeries a non-template class 1st step to make `GraphTrack` not templated based on the data series `Dimension`. Bug: #4534 Test: unit tests * fixup
1 parent 87775a6 commit 074e46f

File tree

8 files changed

+197
-183
lines changed

8 files changed

+197
-183
lines changed

src/OrbitGl/BasicPageFaultsTrack.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <string_view>
1212

1313
#include "OrbitGl/GlCanvas.h"
14-
#include "OrbitGl/MultivariateTimeSeries.h"
1514
#include "OrbitGl/PickingManager.h"
1615

1716
namespace orbit_gl {

src/OrbitGl/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ target_sources(
125125
MajorPageFaultsTrack.cpp
126126
MemoryTrack.cpp
127127
MinorPageFaultsTrack.cpp
128+
MultivariateTimeSeries.cpp
128129
OpenGlBatcher.cpp
129130
OrbitApp.cpp
130131
QtTextRenderer.cpp

src/OrbitGl/GraphTrack.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ GraphTrack<Dimension>::GraphTrack(CaptureViewElement* parent,
4747
const orbit_client_data::ModuleManager* module_manager,
4848
const orbit_client_data::CaptureData* capture_data)
4949
: Track(parent, timeline_info, viewport, layout, module_manager, capture_data),
50-
series_{series_names, series_value_decimal_digits, std::move(series_value_units)} {}
50+
series_{std::vector(series_names.begin(), series_names.end()), series_value_decimal_digits,
51+
std::move(series_value_units)} {}
5152

5253
template <size_t Dimension>
5354
bool GraphTrack<Dimension>::HasLegend() const {
@@ -136,14 +137,14 @@ float GraphTrack<Dimension>::GetLabelYFromValues(
136137
template <size_t Dimension>
137138
std::string GraphTrack<Dimension>::GetLabelTextFromValues(
138139
const std::array<double, Dimension>& values) const {
139-
const std::array<std::string, Dimension>& series_names = series_.GetSeriesNames();
140+
const std::vector<std::string>& series_names = series_.GetSeriesNames();
140141
std::optional<uint8_t> value_decimal_digits = series_.GetValueDecimalDigits();
141142
std::string value_unit = series_.GetValueUnit();
142143
std::string text;
143144
std::string_view delimiter = "";
144145
for (int i = Dimension - 1; i >= 0; i--) {
145146
std::string formatted_name =
146-
series_names[i].empty() ? "" : absl::StrFormat("%s: ", series_names[i]);
147+
series_names.at(i).empty() ? "" : absl::StrFormat("%s: ", series_names.at(i));
147148
std::string formatted_value =
148149
absl::StrFormat("%.*f", value_decimal_digits.value_or(6), values[i]);
149150
absl::StrAppend(&text, delimiter, formatted_name, formatted_value, value_unit);
@@ -175,8 +176,12 @@ void GraphTrack<Dimension>::DrawMouseLabel(PrimitiveAssembler& primitive_assembl
175176
const Color transparent_white(255, 255, 255, 180);
176177

177178
uint64_t current_mouse_time_ns = draw_context.current_mouse_tick.value();
178-
const std::array<double, Dimension>& values =
179-
series_.GetPreviousOrFirstEntry(current_mouse_time_ns);
179+
180+
// TODO(vickyliu): remove this vector to array conversion after changing to not use array.
181+
const std::vector<double>& values_tmp = series_.GetPreviousOrFirstEntry(current_mouse_time_ns);
182+
ORBIT_CHECK(values_tmp.size() == Dimension);
183+
std::array<double, Dimension> values;
184+
std::copy_n(values_tmp.begin(), Dimension, values.begin());
180185
uint64_t first_time = series_.StartTimeInNs();
181186
uint64_t label_time = std::max(current_mouse_time_ns, first_time);
182187
Vec2 target_point_pos{timeline_info_->GetWorldFromTick(label_time), GetLabelYFromValues(values)};
@@ -225,7 +230,7 @@ void GraphTrack<Dimension>::DrawMouseLabel(PrimitiveAssembler& primitive_assembl
225230
template <size_t Dimension>
226231
void GraphTrack<Dimension>::DrawLegend(PrimitiveAssembler& primitive_assembler,
227232
TextRenderer& text_renderer,
228-
const std::array<std::string, Dimension>& series_names,
233+
absl::Span<const std::string> series_names,
229234
const Color& legend_text_color) {
230235
const float space_between_legend_symbol_and_text = layout_->GetGenericFixedSpacerWidth();
231236
const float space_between_legend_entries = layout_->GetGenericFixedSpacerWidth() * 2;
@@ -244,13 +249,13 @@ void GraphTrack<Dimension>::DrawLegend(PrimitiveAssembler& primitive_assembler,
244249
x0 += legend_symbol_width + space_between_legend_symbol_and_text;
245250

246251
const float legend_text_width =
247-
text_renderer.GetStringWidth(series_names[i].c_str(), font_size);
252+
text_renderer.GetStringWidth(series_names.at(i).c_str(), font_size);
248253
const Vec2 legend_text_box_size(legend_text_width, layout_->GetTextBoxHeight());
249254

250255
TextRenderer::TextFormatting formatting{font_size, legend_text_color, legend_text_box_size[0]};
251256
formatting.valign = TextRenderer::VAlign::Middle;
252257

253-
text_renderer.AddText(series_names[i].c_str(), x0, y0 + legend_symbol_height / 2.f, text_z,
258+
text_renderer.AddText(series_names.at(i).c_str(), x0, y0 + legend_symbol_height / 2.f, text_z,
254259
formatting);
255260
auto user_data = std::make_unique<PickingUserData>(
256261
nullptr, [this, i](PickingId /*id*/) { return GetLegendTooltips(i); });
@@ -271,6 +276,7 @@ void GraphTrack<Dimension>::DrawSeries(PrimitiveAssembler& primitive_assembler,
271276
double inverse_value_range = GetInverseOfGraphValueRange();
272277
auto current_it = entries.begin();
273278
auto last_it = std::prev(entries.end());
279+
ORBIT_CHECK(current_it->second.size() == Dimension);
274280

275281
GraphTrackDataAggregator<Dimension> aggr;
276282

@@ -281,7 +287,7 @@ void GraphTrack<Dimension>::DrawSeries(PrimitiveAssembler& primitive_assembler,
281287
// We skip the last element because we can't calculate time passed between last element
282288
// and the next one.
283289
while (current_it != last_it) {
284-
std::array<double, Dimension> cumulative_values{current_it->second};
290+
std::vector<double> cumulative_values = current_it->second;
285291
std::partial_sum(cumulative_values.begin(), cumulative_values.end(), cumulative_values.begin());
286292
// For the stacked graph, computing y positions from the normalized values results in some
287293
// floating error. Event if the sum of values is fixed, the top of the stacked graph may not be

src/OrbitGl/LineGraphTrack.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace orbit_gl {
2424

2525
template <size_t Dimension>
2626
[[nodiscard]] static std::array<float, Dimension> GetNormalizedValues(
27-
const std::array<double, Dimension>& values, double min, double inverse_value_range) {
27+
absl::Span<const double> values, double min, double inverse_value_range) {
2828
std::array<float, Dimension> normalized_values;
2929
std::transform(values.begin(), values.end(), normalized_values.begin(),
3030
[min, inverse_value_range](double value) {
@@ -41,7 +41,7 @@ float LineGraphTrack<Dimension>::GetLabelYFromValues(
4141
double min = this->GetGraphMinValue();
4242
double inverse_value_range = this->GetInverseOfGraphValueRange();
4343
std::array<float, Dimension> normalized_values =
44-
GetNormalizedValues(values, min, inverse_value_range);
44+
GetNormalizedValues<Dimension>(values, min, inverse_value_range);
4545
// The label will point to the only value.
4646
if (Dimension == 1) return base_y - normalized_values[0] * content_height;
4747

@@ -63,7 +63,7 @@ void LineGraphTrack<Dimension>::DrawSeries(PrimitiveAssembler& primitive_assembl
6363

6464
// Normalized values that were last used for drawing.
6565
std::array<float, Dimension> prev_drawn_values =
66-
GetNormalizedValues(curr_iterator->second, min, inverse_value_range);
66+
GetNormalizedValues<Dimension>(curr_iterator->second, min, inverse_value_range);
6767

6868
// Normalized values of the last entry we've iterated over.
6969
std::array<float, Dimension> last_entry_values = prev_drawn_values;
@@ -113,7 +113,7 @@ void LineGraphTrack<Dimension>::DrawSeries(PrimitiveAssembler& primitive_assembl
113113

114114
uint64_t curr_time = curr_iterator->first;
115115
std::array<float, Dimension> curr_normalized_values =
116-
GetNormalizedValues(curr_iterator->second, min, inverse_value_range);
116+
GetNormalizedValues<Dimension>(curr_iterator->second, min, inverse_value_range);
117117

118118
if (aggr.GetAccumulatedEntry() == nullptr) {
119119
aggr.SetEntry(prev_time, curr_time, curr_normalized_values);
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) 2023 The Orbit Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "OrbitGl/MultivariateTimeSeries.h"
6+
7+
#include "OrbitBase/Logging.h"
8+
9+
namespace orbit_gl {
10+
11+
MultivariateTimeSeries::MultivariateTimeSeries(std::vector<std::string> series_names,
12+
uint8_t value_decimal_digits, std::string value_unit)
13+
: series_names_{std::move(series_names)},
14+
value_decimal_digits_{value_decimal_digits},
15+
value_unit_{std::move(value_unit)} {
16+
ORBIT_CHECK(!series_names_.empty());
17+
}
18+
19+
double MultivariateTimeSeries::GetMin() const {
20+
absl::MutexLock lock(&mutex_);
21+
return min_;
22+
}
23+
24+
double MultivariateTimeSeries::GetMax() const {
25+
absl::MutexLock lock(&mutex_);
26+
return max_;
27+
}
28+
29+
bool MultivariateTimeSeries::IsEmpty() const {
30+
absl::MutexLock lock(&mutex_);
31+
return time_to_series_values_.empty();
32+
}
33+
34+
size_t MultivariateTimeSeries::GetTimeToSeriesValuesSize() const {
35+
absl::MutexLock lock(&mutex_);
36+
return time_to_series_values_.size();
37+
}
38+
39+
uint64_t MultivariateTimeSeries::StartTimeInNs() const {
40+
absl::MutexLock lock(&mutex_);
41+
ORBIT_CHECK(!time_to_series_values_.empty());
42+
return time_to_series_values_.begin()->first;
43+
}
44+
45+
uint64_t MultivariateTimeSeries::EndTimeInNs() const {
46+
absl::MutexLock lock(&mutex_);
47+
ORBIT_CHECK(!time_to_series_values_.empty());
48+
return time_to_series_values_.rbegin()->first;
49+
}
50+
51+
std::vector<double> MultivariateTimeSeries::GetPreviousOrFirstEntry(uint64_t time) const {
52+
absl::MutexLock lock(&mutex_);
53+
return GetPreviousOrFirstEntryIterator(time)->second;
54+
}
55+
56+
std::vector<std::pair<uint64_t, std::vector<double>>>
57+
MultivariateTimeSeries::GetEntriesAffectedByTimeRange(uint64_t min_time, uint64_t max_time) const {
58+
absl::MutexLock lock(&mutex_);
59+
if (time_to_series_values_.empty() || min_time >= max_time ||
60+
min_time >= time_to_series_values_.rbegin()->first ||
61+
max_time <= time_to_series_values_.begin()->first) {
62+
return {};
63+
}
64+
65+
auto current_it = GetPreviousOrFirstEntryIterator(min_time);
66+
auto last_iterator = GetNextOrLastEntryIterator(max_time);
67+
68+
std::vector<std::pair<uint64_t, std::vector<double>>> result;
69+
result.push_back(*current_it);
70+
do {
71+
++current_it;
72+
result.push_back(*current_it);
73+
} while (current_it != last_iterator);
74+
75+
return result;
76+
}
77+
78+
void MultivariateTimeSeries::AddValues(uint64_t timestamp_ns, absl::Span<const double> values) {
79+
ORBIT_CHECK(values.size() == series_names_.size());
80+
81+
absl::MutexLock lock(&mutex_);
82+
time_to_series_values_[timestamp_ns] = std::vector(values.begin(), values.end());
83+
for (double value : values) UpdateMinAndMax(value);
84+
}
85+
86+
MultivariateTimeSeries::TimeSeriesEntryIter MultivariateTimeSeries::GetPreviousOrFirstEntryIterator(
87+
uint64_t time) const {
88+
ORBIT_CHECK(!time_to_series_values_.empty());
89+
90+
auto iterator_lower = time_to_series_values_.upper_bound(time);
91+
if (iterator_lower != time_to_series_values_.begin()) --iterator_lower;
92+
return iterator_lower;
93+
}
94+
95+
MultivariateTimeSeries::TimeSeriesEntryIter MultivariateTimeSeries::GetNextOrLastEntryIterator(
96+
uint64_t time) const {
97+
ORBIT_CHECK(!time_to_series_values_.empty());
98+
99+
auto iterator_higher = time_to_series_values_.lower_bound(time);
100+
if (iterator_higher == time_to_series_values_.end()) --iterator_higher;
101+
return iterator_higher;
102+
}
103+
104+
void MultivariateTimeSeries::UpdateMinAndMax(double value) {
105+
max_ = std::max(max_, value);
106+
min_ = std::min(min_, value);
107+
}
108+
109+
} // namespace orbit_gl

src/OrbitGl/MultivariateTimeSeriesTest.cpp

Lines changed: 33 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,41 @@
1515

1616
namespace orbit_gl {
1717

18-
static constexpr uint8_t kDefaultValueDesimalDigits = 6;
19-
static constexpr const char* kDefaultValueUnits = "";
18+
const std::vector<std::string> kSeriesNames{"Series A", "Series B", "Series C"};
19+
static constexpr uint8_t kDefaultValueDecimalDigits = 6;
20+
static constexpr const char* kDefaultValueUnits = "Unit";
21+
22+
constexpr uint64_t kTimestamp1 = 100;
23+
constexpr std::array<double, 3> kValues1{1.1, 1.2, 1.3};
24+
constexpr uint64_t kTimestamp2 = 200;
25+
constexpr std::array<double, 3> kValues2{2.1, 2.2, 2.3};
26+
constexpr uint64_t kTimestamp3 = 300;
27+
constexpr std::array<double, 3> kValues3{3.1, 3.2, 3.3};
28+
29+
void AddTestValuesToSeries(MultivariateTimeSeries& series) {
30+
series.AddValues(kTimestamp1, kValues1);
31+
series.AddValues(kTimestamp2, kValues2);
32+
series.AddValues(kTimestamp3, kValues3);
33+
}
2034

2135
TEST(MultivariateTimeSeries, BasicSetAndGet) {
22-
std::array<std::string, 3> series_names = {"Series A", "Series B", "Series C"};
23-
MultivariateTimeSeries<3> series =
24-
MultivariateTimeSeries<3>(series_names, kDefaultValueDesimalDigits, kDefaultValueUnits);
25-
EXPECT_EQ(series.GetSeriesNames(), series_names);
36+
MultivariateTimeSeries series{kSeriesNames, kDefaultValueDecimalDigits, kDefaultValueUnits};
37+
EXPECT_EQ(series.GetSeriesNames(), kSeriesNames);
38+
EXPECT_EQ(series.GetValueDecimalDigits(), kDefaultValueDecimalDigits);
39+
EXPECT_EQ(series.GetValueUnit(), kDefaultValueUnits);
2640
EXPECT_TRUE(series.IsEmpty());
2741

28-
uint64_t timestamp_1 = 100;
29-
std::array<double, 3> values_1 = {1.1, 1.2, 1.3};
30-
uint64_t timestamp_2 = 200;
31-
std::array<double, 3> values_2 = {2.1, 2.2, 2.3};
32-
uint64_t timestamp_3 = 300;
33-
std::array<double, 3> values_3 = {3.1, 3.2, 3.3};
34-
series.AddValues(timestamp_1, values_1);
35-
series.AddValues(timestamp_2, values_2);
36-
series.AddValues(timestamp_3, values_3);
37-
EXPECT_FALSE(series.IsEmpty());
38-
EXPECT_EQ(series.GetMax(), values_3[2]);
39-
EXPECT_EQ(series.GetMin(), values_1[0]);
40-
EXPECT_EQ(series.StartTimeInNs(), timestamp_1);
41-
EXPECT_EQ(series.EndTimeInNs(), timestamp_3);
42+
AddTestValuesToSeries(series);
43+
EXPECT_EQ(series.GetTimeToSeriesValuesSize(), 3);
44+
EXPECT_EQ(series.GetMin(), kValues1[0]);
45+
EXPECT_EQ(series.GetMax(), kValues3[2]);
46+
EXPECT_EQ(series.StartTimeInNs(), kTimestamp1);
47+
EXPECT_EQ(series.EndTimeInNs(), kTimestamp3);
4248
}
4349

4450
TEST(MultivariateTimeSeries, GetPreviousOrFirstEntry) {
45-
MultivariateTimeSeries<3> series = MultivariateTimeSeries<3>(
46-
{"Series A", "Series B", "Series C"}, kDefaultValueDesimalDigits, kDefaultValueUnits);
47-
uint64_t timestamp_1 = 100;
48-
std::array<double, 3> values_1 = {1.1, 1.2, 1.3};
49-
uint64_t timestamp_2 = 200;
50-
std::array<double, 3> values_2 = {2.1, 2.2, 2.3};
51-
uint64_t timestamp_3 = 300;
52-
std::array<double, 3> values_3 = {3.1, 3.2, 3.3};
53-
series.AddValues(timestamp_1, values_1);
54-
series.AddValues(timestamp_2, values_2);
55-
series.AddValues(timestamp_3, values_3);
51+
MultivariateTimeSeries series{kSeriesNames, kDefaultValueDecimalDigits, kDefaultValueUnits};
52+
AddTestValuesToSeries(series);
5653

5754
{
5855
uint64_t timestamp_before_first_time = 50;
@@ -74,17 +71,8 @@ TEST(MultivariateTimeSeries, GetPreviousOrFirstEntry) {
7471
}
7572

7673
TEST(MultivariateTimeSeries, GetEntriesAffectedByTimeRange) {
77-
MultivariateTimeSeries<3> series = MultivariateTimeSeries<3>(
78-
{"Series A", "Series B", "Series C"}, kDefaultValueDesimalDigits, kDefaultValueUnits);
79-
uint64_t timestamp_1 = 100;
80-
std::array<double, 3> values_1 = {1.1, 1.2, 1.3};
81-
uint64_t timestamp_2 = 200;
82-
std::array<double, 3> values_2 = {2.1, 2.2, 2.3};
83-
uint64_t timestamp_3 = 300;
84-
std::array<double, 3> values_3 = {3.1, 3.2, 3.3};
85-
series.AddValues(timestamp_1, values_1);
86-
series.AddValues(timestamp_2, values_2);
87-
series.AddValues(timestamp_3, values_3);
74+
MultivariateTimeSeries series{kSeriesNames, kDefaultValueDecimalDigits, kDefaultValueUnits};
75+
AddTestValuesToSeries(series);
8876

8977
{
9078
// Test the case that min_time >= max time
@@ -108,24 +96,13 @@ TEST(MultivariateTimeSeries, GetEntriesAffectedByTimeRange) {
10896
uint64_t max_time = 400;
10997
auto entries = series.GetEntriesAffectedByTimeRange(min_time, max_time);
11098
ASSERT_EQ(entries.size(), 3);
111-
EXPECT_EQ(entries[0].first, timestamp_1);
99+
EXPECT_EQ(entries[0].first, kTimestamp1);
112100
EXPECT_THAT(entries[0].second, testing::ElementsAre(1.1, 1.2, 1.3));
113-
EXPECT_EQ(entries[1].first, timestamp_2);
101+
EXPECT_EQ(entries[1].first, kTimestamp2);
114102
EXPECT_THAT(entries[1].second, testing::ElementsAre(2.1, 2.2, 2.3));
115-
EXPECT_EQ(entries[2].first, timestamp_3);
103+
EXPECT_EQ(entries[2].first, kTimestamp3);
116104
EXPECT_THAT(entries[2].second, testing::ElementsAre(3.1, 3.2, 3.3));
117105
}
118106
}
119107

120-
TEST(MultivariateTimeSeries, GetValueDecimalDigits) {
121-
MultivariateTimeSeries<3> series =
122-
MultivariateTimeSeries<3>({"Series A", "Series B", "Series C"}, 42, kDefaultValueUnits);
123-
EXPECT_EQ(series.GetValueDecimalDigits(), 42);
124-
}
125-
126-
TEST(MultivariateTimeSeries, GetValueUnits) {
127-
MultivariateTimeSeries<3> series = MultivariateTimeSeries<3>(
128-
{"Series A", "Series B", "Series C"}, kDefaultValueDesimalDigits, "Meeples");
129-
EXPECT_EQ(series.GetValueUnit(), "Meeples");
130-
}
131108
} // namespace orbit_gl

src/OrbitGl/include/OrbitGl/GraphTrack.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class GraphTrack : public Track {
112112
const DrawContext& draw_context);
113113
virtual void DrawLegend(orbit_gl::PrimitiveAssembler& primitive_assembler,
114114
orbit_gl::TextRenderer& text_renderer,
115-
const std::array<std::string, Dimension>& series_names,
115+
absl::Span<const std::string> series_names,
116116
const Color& legend_text_color);
117117
virtual void DrawSeries(orbit_gl::PrimitiveAssembler& primitive_assembler, uint64_t min_tick,
118118
uint64_t max_tick, float z);
@@ -122,7 +122,7 @@ class GraphTrack : public Track {
122122
std::pow(10, GetNumberOfDecimalDigits());
123123
}
124124

125-
MultivariateTimeSeries<Dimension> series_;
125+
orbit_gl::MultivariateTimeSeries series_;
126126

127127
private:
128128
[[nodiscard]] virtual std::string GetLegendTooltips(size_t legend_index) const = 0;

0 commit comments

Comments
 (0)