[Stats] Introduce Attribute, implementing RTCStatsMemberInterface.
The plan is to replace Members() with Attributes() instead.
For backwards-compatability during the transition, Attribute implements
RTCStatsMemberInterface but the two classes serve the same purpose
which is to allow iterating all metrics of a stats object.
The reason for moving away from "members" is that we already have a way
to express a variable that maybe has a value: absl::optional<T>. The
only information the member adds is the const char* name(), which we'll
move to Attribute in a future CL.
We don't need to maintain an RTCStatsMemberInterface::Type enum in the
future because absl::variant<T> has absl::holds_alternative<T>.
Step 1: Add Attributes().
Step 2: Migrate to Attributes() and delete Members().
Step 3: Replaces all uses of RTCStatsMember<T> with absl::optional<T>
and delete RTCStatsMember + RTCStatsMemberInterface.
Bug: webrtc:15164
Change-Id: I3fdd5b24214bb5cc340a54a0171df73b516e1803
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/333840
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#41507}
This commit is contained in:
parent
24b034c51b
commit
7978cf1b43
@ -771,6 +771,7 @@ rtc_source_set("rtc_stats_api") {
|
||||
visibility = [ "*" ]
|
||||
cflags = []
|
||||
sources = [
|
||||
"stats/attribute.h",
|
||||
"stats/rtc_stats.h",
|
||||
"stats/rtc_stats_collector_callback.h",
|
||||
"stats/rtc_stats_member.h",
|
||||
@ -789,7 +790,10 @@ rtc_source_set("rtc_stats_api") {
|
||||
"units:timestamp",
|
||||
]
|
||||
|
||||
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
|
||||
absl_deps = [
|
||||
"//third_party/abseil-cpp/absl/types:optional",
|
||||
"//third_party/abseil-cpp/absl/types:variant",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_library("audio_options_api") {
|
||||
|
||||
79
api/stats/attribute.h
Normal file
79
api/stats/attribute.h
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2024 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_STATS_ATTRIBUTE_H_
|
||||
#define API_STATS_ATTRIBUTE_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/variant.h"
|
||||
#include "api/stats/rtc_stats_member.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A light-weight wrapper of an RTCStats attribute (an individual metric).
|
||||
class RTC_EXPORT Attribute : public RTCStatsMemberInterface {
|
||||
public:
|
||||
// TODO(https://crbug.com/webrtc/15164): Replace uses of RTCStatsMember<T>
|
||||
// with absl::optional<T> and update these pointer types.
|
||||
typedef absl::variant<const RTCStatsMember<bool>*,
|
||||
const RTCStatsMember<int32_t>*,
|
||||
const RTCStatsMember<uint32_t>*,
|
||||
const RTCStatsMember<int64_t>*,
|
||||
const RTCStatsMember<uint64_t>*,
|
||||
const RTCStatsMember<double>*,
|
||||
const RTCStatsMember<std::string>*,
|
||||
const RTCStatsMember<std::vector<bool>>*,
|
||||
const RTCStatsMember<std::vector<int32_t>>*,
|
||||
const RTCStatsMember<std::vector<uint32_t>>*,
|
||||
const RTCStatsMember<std::vector<int64_t>>*,
|
||||
const RTCStatsMember<std::vector<uint64_t>>*,
|
||||
const RTCStatsMember<std::vector<double>>*,
|
||||
const RTCStatsMember<std::vector<std::string>>*,
|
||||
const RTCStatsMember<std::map<std::string, uint64_t>>*,
|
||||
const RTCStatsMember<std::map<std::string, double>>*>
|
||||
StatVariant;
|
||||
|
||||
template <typename T>
|
||||
explicit Attribute(const RTCStatsMember<T>* attribute)
|
||||
: RTCStatsMemberInterface(attribute->name()), attribute_(attribute) {}
|
||||
~Attribute() override;
|
||||
|
||||
const char* name() const;
|
||||
const StatVariant& as_variant() const;
|
||||
|
||||
static Attribute FromMemberInterface(const RTCStatsMemberInterface* member);
|
||||
// RTCStatsMemberInterface implementation.
|
||||
// TODO(https://crbug.com/webrtc/15164): Delete RTCStatsMemberInterface in
|
||||
// favor of absl::optional<T>.
|
||||
RTCStatsMemberInterface::Type type() const override;
|
||||
bool is_sequence() const override;
|
||||
bool is_string() const override;
|
||||
bool is_defined() const override;
|
||||
std::string ValueToString() const override;
|
||||
std::string ValueToJson() const override;
|
||||
|
||||
protected:
|
||||
const RTCStatsMemberInterface* member_ptr() const override;
|
||||
bool IsEqual(const RTCStatsMemberInterface& other) const override;
|
||||
|
||||
private:
|
||||
StatVariant attribute_;
|
||||
};
|
||||
|
||||
Attribute MemberToAttribute(const RTCStatsMemberInterface* member);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_STATS_ATTRIBUTE_H_
|
||||
@ -20,6 +20,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "api/stats/attribute.h"
|
||||
#include "api/stats/rtc_stats_member.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/checks.h"
|
||||
@ -56,8 +57,8 @@ class RTC_EXPORT RTCStats {
|
||||
public:
|
||||
RTCStats(const std::string& id, Timestamp timestamp)
|
||||
: id_(id), timestamp_(timestamp) {}
|
||||
|
||||
virtual ~RTCStats() {}
|
||||
RTCStats(const RTCStats& other);
|
||||
virtual ~RTCStats();
|
||||
|
||||
virtual std::unique_ptr<RTCStats> copy() const = 0;
|
||||
|
||||
@ -67,9 +68,12 @@ class RTC_EXPORT RTCStats {
|
||||
|
||||
// Returns the static member variable `kType` of the implementing class.
|
||||
virtual const char* type() const = 0;
|
||||
// Returns a vector of pointers to all the `RTCStatsMemberInterface` members
|
||||
// of this class. This allows for iteration of members. For a given class,
|
||||
// `Members` always returns the same members in the same order.
|
||||
// Returns all attributes of this stats object, i.e. a list of its individual
|
||||
// metrics as viewed via the Attribute wrapper.
|
||||
std::vector<Attribute> Attributes() const;
|
||||
// Returns Attributes() as `RTCStatsMemberInterface` pointers.
|
||||
// TODO(https://crbug.com/webrtc/15164): Update callers to use Attributes()
|
||||
// instead and delete this method as well as the RTCStatsMemberInterface.
|
||||
std::vector<const RTCStatsMemberInterface*> Members() const;
|
||||
// Checks if the two stats objects are of the same type and have the same
|
||||
// member values. Timestamps are not compared. These operators are exposed for
|
||||
@ -90,15 +94,18 @@ class RTC_EXPORT RTCStats {
|
||||
}
|
||||
|
||||
protected:
|
||||
// Gets a vector of all members of this `RTCStats` object, including members
|
||||
// derived from parent classes. `additional_capacity` is how many more members
|
||||
// shall be reserved in the vector (so that subclasses can allocate a vector
|
||||
// with room for both parent and child members without it having to resize).
|
||||
virtual std::vector<const RTCStatsMemberInterface*>
|
||||
MembersOfThisObjectAndAncestors(size_t additional_capacity) const;
|
||||
virtual std::vector<Attribute> AttributesImpl(
|
||||
size_t additional_capacity) const;
|
||||
|
||||
std::string const id_;
|
||||
Timestamp timestamp_;
|
||||
|
||||
// Because Members() return a raw pointers we need to cache attributes to
|
||||
// ensure the pointers are still valid after the method has returned. Mutable
|
||||
// to allow lazy instantiation the first time the method is called.
|
||||
// TODO(https://crbug.com/webrtc/15164): Migrate all uses of Members() to
|
||||
// Attributes() and delete Members() and `cached_attributes_`.
|
||||
mutable std::vector<Attribute> cached_attributes_;
|
||||
};
|
||||
|
||||
// All `RTCStats` classes should use these macros.
|
||||
@ -142,16 +149,15 @@ class RTC_EXPORT RTCStats {
|
||||
// bar("bar") {
|
||||
// }
|
||||
//
|
||||
#define WEBRTC_RTCSTATS_DECL() \
|
||||
protected: \
|
||||
std::vector<const webrtc::RTCStatsMemberInterface*> \
|
||||
MembersOfThisObjectAndAncestors(size_t local_var_additional_capacity) \
|
||||
const override; \
|
||||
\
|
||||
public: \
|
||||
static const char kType[]; \
|
||||
\
|
||||
std::unique_ptr<webrtc::RTCStats> copy() const override; \
|
||||
#define WEBRTC_RTCSTATS_DECL() \
|
||||
protected: \
|
||||
std::vector<webrtc::Attribute> AttributesImpl(size_t additional_capacity) \
|
||||
const override; \
|
||||
\
|
||||
public: \
|
||||
static const char kType[]; \
|
||||
\
|
||||
std::unique_ptr<webrtc::RTCStats> copy() const override; \
|
||||
const char* type() const override
|
||||
|
||||
#define WEBRTC_RTCSTATS_IMPL(this_class, parent_class, type_str, ...) \
|
||||
@ -165,23 +171,17 @@ class RTC_EXPORT RTCStats {
|
||||
return this_class::kType; \
|
||||
} \
|
||||
\
|
||||
std::vector<const webrtc::RTCStatsMemberInterface*> \
|
||||
this_class::MembersOfThisObjectAndAncestors( \
|
||||
size_t local_var_additional_capacity) const { \
|
||||
const webrtc::RTCStatsMemberInterface* local_var_members[] = { \
|
||||
__VA_ARGS__}; \
|
||||
size_t local_var_members_count = \
|
||||
sizeof(local_var_members) / sizeof(local_var_members[0]); \
|
||||
std::vector<const webrtc::RTCStatsMemberInterface*> \
|
||||
local_var_members_vec = parent_class::MembersOfThisObjectAndAncestors( \
|
||||
local_var_members_count + local_var_additional_capacity); \
|
||||
RTC_DCHECK_GE( \
|
||||
local_var_members_vec.capacity() - local_var_members_vec.size(), \
|
||||
local_var_members_count + local_var_additional_capacity); \
|
||||
local_var_members_vec.insert(local_var_members_vec.end(), \
|
||||
&local_var_members[0], \
|
||||
&local_var_members[local_var_members_count]); \
|
||||
return local_var_members_vec; \
|
||||
std::vector<webrtc::Attribute> this_class::AttributesImpl( \
|
||||
size_t additional_capacity) const { \
|
||||
const webrtc::RTCStatsMemberInterface* this_members[] = {__VA_ARGS__}; \
|
||||
size_t this_members_size = sizeof(this_members) / sizeof(this_members[0]); \
|
||||
std::vector<webrtc::Attribute> attributes = \
|
||||
parent_class::AttributesImpl(this_members_size + additional_capacity); \
|
||||
for (size_t i = 0; i < this_members_size; ++i) { \
|
||||
attributes.push_back( \
|
||||
webrtc::Attribute::FromMemberInterface(this_members[i])); \
|
||||
} \
|
||||
return attributes; \
|
||||
}
|
||||
|
||||
// A version of WEBRTC_RTCSTATS_IMPL() where "..." is omitted, used to avoid a
|
||||
@ -198,10 +198,9 @@ class RTC_EXPORT RTCStats {
|
||||
return this_class::kType; \
|
||||
} \
|
||||
\
|
||||
std::vector<const webrtc::RTCStatsMemberInterface*> \
|
||||
this_class::MembersOfThisObjectAndAncestors( \
|
||||
size_t local_var_additional_capacity) const { \
|
||||
return parent_class::MembersOfThisObjectAndAncestors(0); \
|
||||
std::vector<webrtc::Attribute> this_class::AttributesImpl( \
|
||||
size_t additional_capacity) const { \
|
||||
return parent_class::AttributesImpl(0); \
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -74,10 +74,11 @@ class RTCStatsMemberInterface {
|
||||
// instead.
|
||||
virtual std::string ValueToJson() const = 0;
|
||||
|
||||
virtual const RTCStatsMemberInterface* member_ptr() const { return this; }
|
||||
template <typename T>
|
||||
const T& cast_to() const {
|
||||
RTC_DCHECK_EQ(type(), T::StaticType());
|
||||
return static_cast<const T&>(*this);
|
||||
return static_cast<const T&>(*member_ptr());
|
||||
}
|
||||
|
||||
protected:
|
||||
@ -151,7 +152,6 @@ class RTCStatsMember : public RTCStatsMemberInterface {
|
||||
return &(*value_);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool IsEqual(const RTCStatsMemberInterface& other) const override {
|
||||
if (type() != other.type())
|
||||
return false;
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
#include "api/media_stream_track.h"
|
||||
#include "api/rtp_parameters.h"
|
||||
#include "api/rtp_transceiver_direction.h"
|
||||
#include "api/stats/attribute.h"
|
||||
#include "api/stats/rtc_stats.h"
|
||||
#include "api/stats/rtc_stats_report.h"
|
||||
#include "api/stats/rtcstats_objects.h"
|
||||
|
||||
@ -207,13 +207,13 @@ class RTCStatsVerifier {
|
||||
RTC_CHECK(report_);
|
||||
RTC_CHECK(stats_);
|
||||
for (const RTCStatsMemberInterface* member : stats_->Members()) {
|
||||
untested_members_.insert(member);
|
||||
untested_members_.insert(member->member_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
void MarkMemberTested(const RTCStatsMemberInterface& member,
|
||||
bool test_successful) {
|
||||
untested_members_.erase(&member);
|
||||
untested_members_.erase(member.member_ptr());
|
||||
all_tests_successful_ &= test_successful;
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ rtc_library("rtc_stats") {
|
||||
visibility = [ "*" ]
|
||||
cflags = []
|
||||
sources = [
|
||||
"attribute.cc",
|
||||
"rtc_stats.cc",
|
||||
"rtc_stats_member.cc",
|
||||
"rtc_stats_report.cc",
|
||||
@ -28,6 +29,7 @@ rtc_library("rtc_stats") {
|
||||
"../rtc_base:macromagic",
|
||||
"../rtc_base:stringutils",
|
||||
]
|
||||
absl_deps = [ "//third_party/abseil-cpp/absl/types:variant" ]
|
||||
}
|
||||
|
||||
rtc_library("rtc_stats_test_utils") {
|
||||
|
||||
142
stats/attribute.cc
Normal file
142
stats/attribute.cc
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2024 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "api/stats/attribute.h"
|
||||
|
||||
#include "absl/types/variant.h"
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
struct VisitIsSequence {
|
||||
// Any type of vector is a sequence.
|
||||
template <typename T>
|
||||
bool operator()(const RTCStatsMember<std::vector<T>>* attribute) {
|
||||
return true;
|
||||
}
|
||||
// Any other type is not.
|
||||
template <typename T>
|
||||
bool operator()(const RTCStatsMember<T>* attribute) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct VisitIsEqual {
|
||||
template <typename T>
|
||||
bool operator()(const RTCStatsMember<T>* attribute) {
|
||||
return attribute->IsEqual(other);
|
||||
}
|
||||
|
||||
const RTCStatsMemberInterface& other;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Attribute::~Attribute() {}
|
||||
|
||||
// static
|
||||
Attribute Attribute::FromMemberInterface(
|
||||
const RTCStatsMemberInterface* member) {
|
||||
switch (member->type()) {
|
||||
case RTCStatsMemberInterface::Type::kBool:
|
||||
return Attribute(&member->cast_to<RTCStatsMember<bool>>());
|
||||
case RTCStatsMemberInterface::Type::kInt32:
|
||||
return Attribute(&member->cast_to<RTCStatsMember<int32_t>>());
|
||||
case RTCStatsMemberInterface::Type::kUint32:
|
||||
return Attribute(&member->cast_to<RTCStatsMember<uint32_t>>());
|
||||
case RTCStatsMemberInterface::Type::kInt64:
|
||||
return Attribute(&member->cast_to<RTCStatsMember<int64_t>>());
|
||||
case RTCStatsMemberInterface::Type::kUint64:
|
||||
return Attribute(&member->cast_to<RTCStatsMember<uint64_t>>());
|
||||
case RTCStatsMemberInterface::Type::kDouble:
|
||||
return Attribute(&member->cast_to<RTCStatsMember<double>>());
|
||||
case RTCStatsMemberInterface::Type::kString:
|
||||
return Attribute(&member->cast_to<RTCStatsMember<std::string>>());
|
||||
case RTCStatsMemberInterface::Type::kSequenceBool:
|
||||
return Attribute(&member->cast_to<RTCStatsMember<std::vector<bool>>>());
|
||||
case RTCStatsMemberInterface::Type::kSequenceInt32:
|
||||
return Attribute(
|
||||
&member->cast_to<RTCStatsMember<std::vector<int32_t>>>());
|
||||
case RTCStatsMemberInterface::Type::kSequenceUint32:
|
||||
return Attribute(
|
||||
&member->cast_to<RTCStatsMember<std::vector<uint32_t>>>());
|
||||
case RTCStatsMemberInterface::Type::kSequenceInt64:
|
||||
return Attribute(
|
||||
&member->cast_to<RTCStatsMember<std::vector<int64_t>>>());
|
||||
case RTCStatsMemberInterface::Type::kSequenceUint64:
|
||||
return Attribute(
|
||||
&member->cast_to<RTCStatsMember<std::vector<uint64_t>>>());
|
||||
case RTCStatsMemberInterface::Type::kSequenceDouble:
|
||||
return Attribute(&member->cast_to<RTCStatsMember<std::vector<double>>>());
|
||||
case RTCStatsMemberInterface::Type::kSequenceString:
|
||||
return Attribute(
|
||||
&member->cast_to<RTCStatsMember<std::vector<std::string>>>());
|
||||
case RTCStatsMemberInterface::Type::kMapStringUint64:
|
||||
return Attribute(
|
||||
&member->cast_to<RTCStatsMember<std::map<std::string, uint64_t>>>());
|
||||
case RTCStatsMemberInterface::Type::kMapStringDouble:
|
||||
return Attribute(
|
||||
&member->cast_to<RTCStatsMember<std::map<std::string, double>>>());
|
||||
default:
|
||||
RTC_CHECK_NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
const char* Attribute::name() const {
|
||||
return absl::visit([](const auto* attr) { return attr->name(); }, attribute_);
|
||||
}
|
||||
|
||||
const Attribute::StatVariant& Attribute::as_variant() const {
|
||||
return attribute_;
|
||||
}
|
||||
|
||||
RTCStatsMemberInterface::Type Attribute::type() const {
|
||||
return absl::visit([](const auto* attr) { return attr->type(); }, attribute_);
|
||||
}
|
||||
|
||||
const RTCStatsMemberInterface* Attribute::member_ptr() const {
|
||||
return absl::visit(
|
||||
[](const auto* attr) {
|
||||
return static_cast<const RTCStatsMemberInterface*>(attr);
|
||||
},
|
||||
attribute_);
|
||||
}
|
||||
|
||||
bool Attribute::is_sequence() const {
|
||||
return absl::visit(VisitIsSequence(), attribute_);
|
||||
}
|
||||
|
||||
bool Attribute::is_string() const {
|
||||
return absl::holds_alternative<const RTCStatsMember<std::string>*>(
|
||||
attribute_);
|
||||
}
|
||||
|
||||
bool Attribute::is_defined() const {
|
||||
return absl::visit([](const auto* attr) { return attr->is_defined(); },
|
||||
attribute_);
|
||||
}
|
||||
|
||||
std::string Attribute::ValueToString() const {
|
||||
return absl::visit([](const auto* attr) { return attr->ValueToString(); },
|
||||
attribute_);
|
||||
}
|
||||
|
||||
std::string Attribute::ValueToJson() const {
|
||||
return absl::visit([](const auto* attr) { return attr->ValueToJson(); },
|
||||
attribute_);
|
||||
}
|
||||
|
||||
bool Attribute::IsEqual(const RTCStatsMemberInterface& other) const {
|
||||
return absl::visit(VisitIsEqual{.other = *other.member_ptr()}, attribute_);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -16,6 +16,11 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
RTCStats::RTCStats(const RTCStats& other)
|
||||
: RTCStats(other.id_, other.timestamp_) {}
|
||||
|
||||
RTCStats::~RTCStats() {}
|
||||
|
||||
bool RTCStats::operator==(const RTCStats& other) const {
|
||||
if (type() != other.type() || id() != other.id())
|
||||
return false;
|
||||
@ -60,14 +65,26 @@ std::string RTCStats::ToJson() const {
|
||||
}
|
||||
|
||||
std::vector<const RTCStatsMemberInterface*> RTCStats::Members() const {
|
||||
return MembersOfThisObjectAndAncestors(0);
|
||||
}
|
||||
|
||||
std::vector<const RTCStatsMemberInterface*>
|
||||
RTCStats::MembersOfThisObjectAndAncestors(size_t additional_capacity) const {
|
||||
if (cached_attributes_.empty()) {
|
||||
cached_attributes_ = Attributes();
|
||||
}
|
||||
std::vector<const RTCStatsMemberInterface*> members;
|
||||
members.reserve(additional_capacity);
|
||||
members.reserve(cached_attributes_.size());
|
||||
for (const auto& attribute : cached_attributes_) {
|
||||
members.push_back(&attribute);
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
std::vector<Attribute> RTCStats::Attributes() const {
|
||||
return AttributesImpl(0);
|
||||
}
|
||||
|
||||
std::vector<Attribute> RTCStats::AttributesImpl(
|
||||
size_t additional_capacity) const {
|
||||
std::vector<Attribute> attributes;
|
||||
attributes.reserve(additional_capacity);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "api/stats/attribute.h"
|
||||
#include "api/stats/rtc_stats.h"
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user