diff --git a/api/stats/rtc_stats.h b/api/stats/rtc_stats.h index 5de5b7fbb0..9290e803fa 100644 --- a/api/stats/rtc_stats.h +++ b/api/stats/rtc_stats.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -237,6 +238,9 @@ class RTCStatsMemberInterface { kSequenceUint64, // std::vector kSequenceDouble, // std::vector kSequenceString, // std::vector + + kMapStringUint64, // std::map + kMapStringDouble, // std::map }; virtual ~RTCStatsMemberInterface() {} @@ -363,6 +367,13 @@ class RTCStatsMember : public RTCStatsMemberInterface { T value_; }; +namespace rtc_stats_internal { + +typedef std::map MapStringUint64; +typedef std::map MapStringDouble; + +} // namespace rtc_stats_internal + #define WEBRTC_DECLARE_RTCSTATSMEMBER(T) \ template <> \ RTC_EXPORT RTCStatsMemberInterface::Type RTCStatsMember::StaticType(); \ @@ -391,6 +402,8 @@ WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector); WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector); WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector); WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector); +WEBRTC_DECLARE_RTCSTATSMEMBER(rtc_stats_internal::MapStringUint64); +WEBRTC_DECLARE_RTCSTATSMEMBER(rtc_stats_internal::MapStringDouble); // Using inheritance just so that it's obvious from the member's declaration // whether it's standardized or not. @@ -455,6 +468,10 @@ extern template class RTC_EXPORT_TEMPLATE_DECLARE(RTC_EXPORT) RTCNonStandardStatsMember>; extern template class RTC_EXPORT_TEMPLATE_DECLARE(RTC_EXPORT) RTCNonStandardStatsMember>; +extern template class RTC_EXPORT_TEMPLATE_DECLARE(RTC_EXPORT) + RTCNonStandardStatsMember>; +extern template class RTC_EXPORT_TEMPLATE_DECLARE(RTC_EXPORT) + RTCNonStandardStatsMember>; } // namespace webrtc diff --git a/sdk/android/api/org/webrtc/RTCStats.java b/sdk/android/api/org/webrtc/RTCStats.java index 7ad7634c82..573d95300f 100644 --- a/sdk/android/api/org/webrtc/RTCStats.java +++ b/sdk/android/api/org/webrtc/RTCStats.java @@ -62,6 +62,7 @@ public class RTCStats { * - Double * - String * - The array form of any of the above (e.g., Integer[]) + * - Map of String keys to BigInteger / Double values */ public Map getMembers() { return members; diff --git a/sdk/android/src/jni/pc/rtc_stats_collector_callback_wrapper.cc b/sdk/android/src/jni/pc/rtc_stats_collector_callback_wrapper.cc index b334bb4a72..baa3f276e7 100644 --- a/sdk/android/src/jni/pc/rtc_stats_collector_callback_wrapper.cc +++ b/sdk/android/src/jni/pc/rtc_stats_collector_callback_wrapper.cc @@ -94,6 +94,23 @@ ScopedJavaLocalRef MemberToJava( case RTCStatsMemberInterface::kSequenceString: return NativeToJavaStringArray( env, *member.cast_to>>()); + + case RTCStatsMemberInterface::kMapStringUint64: + return NativeToJavaMap( + env, + *member.cast_to>>(), + [](JNIEnv* env, const auto& entry) { + return std::make_pair(NativeToJavaString(env, entry.first), + NativeToJavaBigInteger(env, entry.second)); + }); + + case RTCStatsMemberInterface::kMapStringDouble: + return NativeToJavaMap( + env, *member.cast_to>>(), + [](JNIEnv* env, const auto& entry) { + return std::make_pair(NativeToJavaString(env, entry.first), + NativeToJavaDouble(env, entry.second)); + }); } RTC_NOTREACHED(); return nullptr; diff --git a/sdk/objc/api/peerconnection/RTCStatisticsReport.h b/sdk/objc/api/peerconnection/RTCStatisticsReport.h index 38d93e8771..06dbf48d88 100644 --- a/sdk/objc/api/peerconnection/RTCStatisticsReport.h +++ b/sdk/objc/api/peerconnection/RTCStatisticsReport.h @@ -44,8 +44,8 @@ RTC_OBJC_EXPORT @property(nonatomic, readonly) NSString *type; /** The keys and values of the subreport, e.g. "totalFramesDuration = 5.551". - The values are either NSNumbers or NSStrings, or NSArrays encapsulating NSNumbers - or NSStrings. */ + The values are either NSNumbers or NSStrings or NSArrays encapsulating NSNumbers + or NSStrings, or NSDictionary of NSString keys to NSNumber values. */ @property(nonatomic, readonly) NSDictionary *values; - (instancetype)init NS_UNAVAILABLE; diff --git a/sdk/objc/api/peerconnection/RTCStatisticsReport.mm b/sdk/objc/api/peerconnection/RTCStatisticsReport.mm index 1dd72772ed..967683fc91 100644 --- a/sdk/objc/api/peerconnection/RTCStatisticsReport.mm +++ b/sdk/objc/api/peerconnection/RTCStatisticsReport.mm @@ -16,7 +16,7 @@ namespace webrtc { /** Converts a single value to a suitable NSNumber, NSString or NSArray containing NSNumbers - or NSStrings.*/ + or NSStrings, or NSDictionary of NSString keys to NSNumber values.*/ NSObject *ValueFromStatsMember(const RTCStatsMemberInterface *member) { if (member->is_defined()) { switch (member->type()) { @@ -91,6 +91,26 @@ NSObject *ValueFromStatsMember(const RTCStatsMemberInterface *member) { } return [array copy]; } + case RTCStatsMemberInterface::kMapStringUint64: { + std::map map = + *member->cast_to>>(); + NSMutableDictionary *dictionary = + [NSMutableDictionary dictionaryWithCapacity:map.size()]; + for (const auto &item : map) { + dictionary[[NSString stringForStdString:item.first]] = @(item.second); + } + return [dictionary copy]; + } + case RTCStatsMemberInterface::kMapStringDouble: { + std::map map = + *member->cast_to>>(); + NSMutableDictionary *dictionary = + [NSMutableDictionary dictionaryWithCapacity:map.size()]; + for (const auto &item : map) { + dictionary[[NSString stringForStdString:item.first]] = @(item.second); + } + return [dictionary copy]; + } default: RTC_NOTREACHED(); } diff --git a/stats/rtc_stats.cc b/stats/rtc_stats.cc index 59de664c0e..4895edc738 100644 --- a/stats/rtc_stats.cc +++ b/stats/rtc_stats.cc @@ -64,6 +64,20 @@ std::string VectorOfStringsToString(const std::vector& strings) { return sb.Release(); } +template +std::string MapToString(const std::map& map) { + rtc::StringBuilder sb; + sb << "{"; + const char* separator = ""; + for (const auto& element : map) { + sb << separator << rtc::ToString(element.first) << ":" + << rtc::ToString(element.second); + separator = ","; + } + sb << "}"; + return sb.Release(); +} + template std::string ToStringAsDouble(const T value) { // JSON represents numbers as floating point numbers with about 15 decimal @@ -88,6 +102,20 @@ std::string VectorToStringAsDouble(const std::vector& vector) { return sb.Release(); } +template +std::string MapToStringAsDouble(const std::map& map) { + rtc::StringBuilder sb; + sb << "{"; + const char* separator = ""; + for (const auto& element : map) { + sb << separator << "\"" << rtc::ToString(element.first) + << "\":" << ToStringAsDouble(element.second); + separator = ","; + } + sb << "}"; + return sb.Release(); +} + } // namespace bool RTCStats::operator==(const RTCStats& other) const { @@ -248,6 +276,18 @@ WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector, false, VectorOfStringsToString(value_), VectorOfStringsToString(value_)); +WEBRTC_DEFINE_RTCSTATSMEMBER(rtc_stats_internal::MapStringUint64, + kMapStringUint64, + false, + false, + MapToString(value_), + MapToStringAsDouble(value_)); +WEBRTC_DEFINE_RTCSTATSMEMBER(rtc_stats_internal::MapStringDouble, + kMapStringDouble, + false, + false, + MapToString(value_), + MapToStringAsDouble(value_)); template class RTC_EXPORT_TEMPLATE_DEFINE(RTC_EXPORT) RTCNonStandardStatsMember; diff --git a/stats/rtc_stats_unittest.cc b/stats/rtc_stats_unittest.cc index b159977858..aff6ab30bd 100644 --- a/stats/rtc_stats_unittest.cc +++ b/stats/rtc_stats_unittest.cc @@ -71,7 +71,7 @@ TEST(RTCStatsTest, RTCStatsAndMembers) { EXPECT_EQ(stats.id(), "testId"); EXPECT_EQ(stats.timestamp_us(), static_cast(42)); std::vector members = stats.Members(); - EXPECT_EQ(members.size(), static_cast(14)); + EXPECT_EQ(members.size(), static_cast(16)); for (const RTCStatsMemberInterface* member : members) { EXPECT_FALSE(member->is_defined()); } @@ -98,6 +98,9 @@ TEST(RTCStatsTest, RTCStatsAndMembers) { std::vector sequence_string; sequence_string.push_back(std::string("six")); + std::map map_string_uint64{{"seven", 8}}; + std::map map_string_double{{"nine", 10.0}}; + stats.m_sequence_bool = sequence_bool; stats.m_sequence_int32 = sequence_int32; stats.m_sequence_uint32 = sequence_uint32; @@ -106,6 +109,8 @@ TEST(RTCStatsTest, RTCStatsAndMembers) { stats.m_sequence_uint64 = sequence_uint64; stats.m_sequence_double = sequence_double; stats.m_sequence_string = sequence_string; + stats.m_map_string_uint64 = map_string_uint64; + stats.m_map_string_double = map_string_double; for (const RTCStatsMemberInterface* member : members) { EXPECT_TRUE(member->is_defined()); } @@ -123,6 +128,8 @@ TEST(RTCStatsTest, RTCStatsAndMembers) { EXPECT_EQ(*stats.m_sequence_uint64, sequence_uint64); EXPECT_EQ(*stats.m_sequence_double, sequence_double); EXPECT_EQ(*stats.m_sequence_string, sequence_string); + EXPECT_EQ(*stats.m_map_string_uint64, map_string_uint64); + EXPECT_EQ(*stats.m_map_string_double, map_string_double); int32_t numbers[] = {4, 8, 15, 16, 23, 42}; std::vector numbers_sequence(&numbers[0], &numbers[6]); @@ -152,6 +159,8 @@ TEST(RTCStatsTest, EqualityOperator) { stats_with_all_values.m_sequence_uint64 = std::vector(); stats_with_all_values.m_sequence_double = std::vector(); stats_with_all_values.m_sequence_string = std::vector(); + stats_with_all_values.m_map_string_uint64 = std::map(); + stats_with_all_values.m_map_string_double = std::map(); EXPECT_NE(stats_with_all_values, empty_stats); EXPECT_EQ(stats_with_all_values, stats_with_all_values); EXPECT_NE(stats_with_all_values.m_int32, stats_with_all_values.m_uint32); @@ -180,6 +189,8 @@ TEST(RTCStatsTest, EqualityOperator) { one_member_different[11].m_sequence_uint64->push_back(321); one_member_different[12].m_sequence_double->push_back(321.0); one_member_different[13].m_sequence_string->push_back("321"); + (*one_member_different[13].m_map_string_uint64)["321"] = 321; + (*one_member_different[13].m_map_string_double)["321"] = 321.0; for (size_t i = 0; i < 14; ++i) { EXPECT_NE(stats_with_all_values, one_member_different[i]); } @@ -238,6 +249,11 @@ TEST(RTCStatsTest, RTCStatsPrintsValidJson) { std::vector sequence_string; sequence_string.push_back(std::string("four")); + std::map map_string_uint64{ + {"long", static_cast(1234567890123456499L)}}; + std::map map_string_double{ + {"three", 123.4567890123456499}, {"thirteen", 123.4567890123456499}}; + RTCTestStats stats(id, timestamp); stats.m_bool = m_bool; stats.m_int32 = m_int32; @@ -249,6 +265,8 @@ TEST(RTCStatsTest, RTCStatsPrintsValidJson) { stats.m_sequence_int64 = sequence_int64; stats.m_sequence_double = sequence_double; stats.m_sequence_string = sequence_string; + stats.m_map_string_uint64 = map_string_uint64; + stats.m_map_string_double = map_string_double; Json::Value json_output; EXPECT_TRUE(Json::Reader().parse(stats.ToJson(), json_output)); @@ -278,6 +296,16 @@ TEST(RTCStatsTest, RTCStatsPrintsValidJson) { rtc::GetValueFromJsonObject(json_output, "mSequenceString", &json_array)); EXPECT_TRUE(rtc::JsonArrayToStringVector(json_array, &sequence_string)); + Json::Value json_map; + EXPECT_TRUE( + rtc::GetValueFromJsonObject(json_output, "mMapStringDouble", &json_map)); + for (const auto& entry : map_string_double) { + double double_output = 0.0; + EXPECT_TRUE( + rtc::GetDoubleFromJsonObject(json_map, entry.first, &double_output)); + EXPECT_NEAR(double_output, entry.second, GetExpectedError(entry.second)); + } + EXPECT_EQ(id, stats.id()); EXPECT_EQ(timestamp, stats.timestamp_us()); EXPECT_EQ(m_bool, *stats.m_bool); @@ -286,6 +314,7 @@ TEST(RTCStatsTest, RTCStatsPrintsValidJson) { EXPECT_EQ(sequence_bool, *stats.m_sequence_bool); EXPECT_EQ(sequence_int32, *stats.m_sequence_int32); EXPECT_EQ(sequence_string, *stats.m_sequence_string); + EXPECT_EQ(map_string_double, *stats.m_map_string_double); EXPECT_NEAR(m_double, *stats.m_double, GetExpectedError(*stats.m_double)); @@ -295,6 +324,13 @@ TEST(RTCStatsTest, RTCStatsPrintsValidJson) { GetExpectedError(stats.m_sequence_double->at(i))); } + EXPECT_EQ(map_string_double.size(), stats.m_map_string_double->size()); + for (const auto& entry : map_string_double) { + auto it = stats.m_map_string_double->find(entry.first); + EXPECT_NE(it, stats.m_map_string_double->end()); + EXPECT_NEAR(entry.second, it->second, GetExpectedError(it->second)); + } + // We read mInt64 as double since JSON stores all numbers as doubles, so there // is not enough precision to represent large numbers. double m_int64_as_double; @@ -320,6 +356,19 @@ TEST(RTCStatsTest, RTCStatsPrintsValidJson) { GetExpectedError(stats_value_as_double)); } + // Similarly, read Uint64 as double + EXPECT_TRUE( + rtc::GetValueFromJsonObject(json_output, "mMapStringUint64", &json_map)); + for (const auto& entry : map_string_uint64) { + const double stats_value_as_double = + static_cast((*stats.m_map_string_uint64)[entry.first]); + double double_output = 0.0; + EXPECT_TRUE( + rtc::GetDoubleFromJsonObject(json_map, entry.first, &double_output)); + EXPECT_NEAR(double_output, stats_value_as_double, + GetExpectedError(stats_value_as_double)); + } + // Neither stats.m_uint32 nor stats.m_uint64 are defined, so "mUint64" and // "mUint32" should not be part of the generated JSON object. int m_uint32; diff --git a/stats/test/rtc_test_stats.cc b/stats/test/rtc_test_stats.cc index d8bcbb19eb..e73da76fa9 100644 --- a/stats/test/rtc_test_stats.cc +++ b/stats/test/rtc_test_stats.cc @@ -30,7 +30,9 @@ WEBRTC_RTCSTATS_IMPL(RTCTestStats, &m_sequence_int64, &m_sequence_uint64, &m_sequence_double, - &m_sequence_string) + &m_sequence_string, + &m_map_string_uint64, + &m_map_string_double) RTCTestStats::RTCTestStats(const std::string& id, int64_t timestamp_us) : RTCStats(id, timestamp_us), @@ -47,7 +49,9 @@ RTCTestStats::RTCTestStats(const std::string& id, int64_t timestamp_us) m_sequence_int64("mSequenceInt64"), m_sequence_uint64("mSequenceUint64"), m_sequence_double("mSequenceDouble"), - m_sequence_string("mSequenceString") {} + m_sequence_string("mSequenceString"), + m_map_string_uint64("mMapStringUint64"), + m_map_string_double("mMapStringDouble") {} RTCTestStats::RTCTestStats(const RTCTestStats& other) : RTCStats(other.id(), other.timestamp_us()), @@ -64,7 +68,9 @@ RTCTestStats::RTCTestStats(const RTCTestStats& other) m_sequence_int64(other.m_sequence_int64), m_sequence_uint64(other.m_sequence_uint64), m_sequence_double(other.m_sequence_double), - m_sequence_string(other.m_sequence_string) {} + m_sequence_string(other.m_sequence_string), + m_map_string_uint64(other.m_map_string_uint64), + m_map_string_double(other.m_map_string_double) {} RTCTestStats::~RTCTestStats() {} diff --git a/stats/test/rtc_test_stats.h b/stats/test/rtc_test_stats.h index 1db32c25c1..0feb07e78e 100644 --- a/stats/test/rtc_test_stats.h +++ b/stats/test/rtc_test_stats.h @@ -12,6 +12,7 @@ #define STATS_TEST_RTC_TEST_STATS_H_ #include +#include #include #include @@ -42,6 +43,8 @@ class RTC_EXPORT RTCTestStats : public RTCStats { RTCStatsMember> m_sequence_uint64; RTCStatsMember> m_sequence_double; RTCStatsMember> m_sequence_string; + RTCStatsMember> m_map_string_uint64; + RTCStatsMember> m_map_string_double; }; } // namespace webrtc