chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
add_library(BUTIL_LIB OBJECT ${BUTIL_SOURCES})
|
||||
add_library(SOURCES_LIB OBJECT ${SOURCES})
|
||||
add_dependencies(SOURCES_LIB PROTO_LIB)
|
||||
target_link_libraries(BUTIL_LIB PRIVATE brpc_common_config)
|
||||
target_link_libraries(SOURCES_LIB PRIVATE brpc_common_config)
|
||||
|
||||
# shared library needs POSITION_INDEPENDENT_CODE
|
||||
set_property(TARGET ${SOURCES_LIB} PROPERTY POSITION_INDEPENDENT_CODE 1)
|
||||
set_property(TARGET ${BUTIL_LIB} PROPERTY POSITION_INDEPENDENT_CODE 1)
|
||||
|
||||
add_library(brpc-static STATIC $<TARGET_OBJECTS:BUTIL_LIB>
|
||||
$<TARGET_OBJECTS:SOURCES_LIB>
|
||||
$<TARGET_OBJECTS:PROTO_LIB>)
|
||||
target_link_libraries(brpc-static PUBLIC brpc_common_config)
|
||||
|
||||
function(check_thrift_version target_arg)
|
||||
#use thrift command to get version
|
||||
execute_process(
|
||||
COMMAND thrift --version
|
||||
OUTPUT_VARIABLE THRIFT_VERSION_OUTPUT
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" THRIFT_VERSION ${THRIFT_VERSION_OUTPUT})
|
||||
string(REGEX REPLACE "\\." ";" THRIFT_VERSION_LIST ${THRIFT_VERSION})
|
||||
|
||||
list(GET THRIFT_VERSION_LIST 0 THRIFT_MAJOR_VERSION)
|
||||
list(GET THRIFT_VERSION_LIST 1 THRIFT_MINOR_VERSION)
|
||||
|
||||
if (THRIFT_MAJOR_VERSION EQUAL 0 AND THRIFT_MINOR_VERSION LESS 11)
|
||||
message(STATUS "Thrift version is less than 0.11.0")
|
||||
target_compile_definitions(${target_arg} PRIVATE _THRIFT_VERSION_LOWER_THAN_0_11_0_)
|
||||
else()
|
||||
message(STATUS "Thrift version is equal to or greater than 0.11.0")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
||||
if(WITH_THRIFT)
|
||||
target_link_libraries(brpc-static PRIVATE ${THRIFT_LIB})
|
||||
check_thrift_version(brpc-static)
|
||||
endif()
|
||||
|
||||
SET_TARGET_PROPERTIES(brpc-static PROPERTIES OUTPUT_NAME brpc CLEAN_DIRECT_OUTPUT 1)
|
||||
|
||||
# for protoc-gen-mcpack
|
||||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/output/bin)
|
||||
|
||||
set(protoc_gen_mcpack_SOURCES
|
||||
${PROJECT_SOURCE_DIR}/src/mcpack2pb/generator.cpp
|
||||
)
|
||||
|
||||
add_executable(protoc-gen-mcpack ${protoc_gen_mcpack_SOURCES})
|
||||
target_link_libraries(protoc-gen-mcpack PRIVATE brpc_common_config)
|
||||
|
||||
if(BUILD_SHARED_LIBS)
|
||||
add_library(brpc-shared SHARED $<TARGET_OBJECTS:BUTIL_LIB>
|
||||
$<TARGET_OBJECTS:SOURCES_LIB>
|
||||
$<TARGET_OBJECTS:PROTO_LIB>)
|
||||
target_link_libraries(brpc-shared PUBLIC brpc_common_config)
|
||||
target_link_libraries(brpc-shared PRIVATE ${DYNAMIC_LIB})
|
||||
if(WITH_GLOG)
|
||||
target_link_libraries(brpc-shared PRIVATE ${GLOG_LIB})
|
||||
endif()
|
||||
if(WITH_THRIFT)
|
||||
target_link_libraries(brpc-shared PRIVATE ${THRIFT_LIB})
|
||||
check_thrift_version(brpc-shared)
|
||||
endif()
|
||||
SET_TARGET_PROPERTIES(brpc-shared PROPERTIES OUTPUT_NAME brpc CLEAN_DIRECT_OUTPUT 1)
|
||||
|
||||
target_link_libraries(protoc-gen-mcpack PRIVATE brpc-shared ${DYNAMIC_LIB} pthread)
|
||||
|
||||
install(TARGETS brpc-shared
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
else()
|
||||
target_link_libraries(protoc-gen-mcpack PRIVATE brpc-static ${DYNAMIC_LIB} pthread)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
install(TARGETS brpc-static
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
@@ -0,0 +1,357 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include "butil/fd_guard.h" // fd_guard
|
||||
#include "butil/fd_utility.h" // make_close_on_exec
|
||||
#include "butil/time.h" // gettimeofday_us
|
||||
#include "brpc/acceptor.h"
|
||||
#include "brpc/transport_factory.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
static const int INITIAL_CONNECTION_CAP = 65536;
|
||||
|
||||
Acceptor::Acceptor(bthread_keytable_pool_t* pool)
|
||||
: InputMessenger()
|
||||
, _keytable_pool(pool)
|
||||
, _status(UNINITIALIZED)
|
||||
, _idle_timeout_sec(-1)
|
||||
, _close_idle_tid(INVALID_BTHREAD)
|
||||
, _listened_fd(-1)
|
||||
, _acception_id(0)
|
||||
, _empty_cond(&_map_mutex)
|
||||
, _force_ssl(false)
|
||||
, _ssl_ctx(NULL)
|
||||
, _socket_mode(SOCKET_MODE_TCP)
|
||||
, _bthread_tag(BTHREAD_TAG_DEFAULT) {
|
||||
}
|
||||
|
||||
Acceptor::~Acceptor() {
|
||||
StopAccept(0);
|
||||
Join();
|
||||
}
|
||||
|
||||
int Acceptor::StartAccept(int listened_fd, int idle_timeout_sec,
|
||||
const std::shared_ptr<SocketSSLContext>& ssl_ctx,
|
||||
bool force_ssl) {
|
||||
if (listened_fd < 0) {
|
||||
LOG(FATAL) << "Invalid listened_fd=" << listened_fd;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!ssl_ctx && force_ssl) {
|
||||
LOG(ERROR) << "Fail to force SSL for all connections "
|
||||
" because ssl_ctx is NULL";
|
||||
return -1;
|
||||
}
|
||||
|
||||
BAIDU_SCOPED_LOCK(_map_mutex);
|
||||
if (_status == UNINITIALIZED) {
|
||||
if (Initialize() != 0) {
|
||||
LOG(FATAL) << "Fail to initialize Acceptor";
|
||||
return -1;
|
||||
}
|
||||
_status = READY;
|
||||
}
|
||||
if (_status != READY) {
|
||||
LOG(FATAL) << "Acceptor hasn't stopped yet: status=" << status();
|
||||
return -1;
|
||||
}
|
||||
if (idle_timeout_sec > 0) {
|
||||
bthread_attr_t tmp = BTHREAD_ATTR_NORMAL;
|
||||
tmp.tag = _bthread_tag;
|
||||
bthread_attr_set_name(&tmp, "CloseIdleConnections");
|
||||
if (bthread_start_background(&_close_idle_tid, &tmp, CloseIdleConnections, this) != 0) {
|
||||
LOG(FATAL) << "Fail to start bthread";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
_idle_timeout_sec = idle_timeout_sec;
|
||||
_force_ssl = force_ssl;
|
||||
_ssl_ctx = ssl_ctx;
|
||||
|
||||
// Creation of _acception_id is inside lock so that OnNewConnections
|
||||
// (which may run immediately) should see sane fields set below.
|
||||
SocketOptions options;
|
||||
options.fd = listened_fd;
|
||||
options.user = this;
|
||||
options.bthread_tag = _bthread_tag;
|
||||
options.on_edge_triggered_events = OnNewConnections;
|
||||
if (Socket::Create(options, &_acception_id) != 0) {
|
||||
// Close-idle-socket thread will be stopped inside destructor
|
||||
LOG(FATAL) << "Fail to create _acception_id";
|
||||
return -1;
|
||||
}
|
||||
|
||||
_listened_fd = listened_fd;
|
||||
_status = RUNNING;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* Acceptor::CloseIdleConnections(void* arg) {
|
||||
Acceptor* am = static_cast<Acceptor*>(arg);
|
||||
std::vector<SocketId> checking_fds;
|
||||
const uint64_t CHECK_INTERVAL_US = 1000000UL;
|
||||
while (bthread_usleep(CHECK_INTERVAL_US) == 0) {
|
||||
// TODO: this is not efficient for a lot of connections(>100K)
|
||||
am->ListConnections(&checking_fds);
|
||||
for (size_t i = 0; i < checking_fds.size(); ++i) {
|
||||
SocketUniquePtr s;
|
||||
if (Socket::Address(checking_fds[i], &s) == 0) {
|
||||
s->ReleaseReferenceIfIdle(am->_idle_timeout_sec);
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void Acceptor::StopAccept(int /*closewait_ms*/) {
|
||||
// Currently `closewait_ms' is useless since we have to wait until
|
||||
// existing requests are finished. Otherwise, contexts depended by
|
||||
// the requests may be deleted and invalid.
|
||||
|
||||
{
|
||||
BAIDU_SCOPED_LOCK(_map_mutex);
|
||||
if (_status != RUNNING) {
|
||||
return;
|
||||
}
|
||||
_status = STOPPING;
|
||||
}
|
||||
|
||||
// Don't set _acception_id to 0 because BeforeRecycle needs it.
|
||||
Socket::SetFailed(_acception_id);
|
||||
|
||||
// SetFailed all existing connections. Connections added after this piece
|
||||
// of code will be SetFailed directly in OnNewConnectionsUntilEAGAIN
|
||||
std::vector<SocketId> erasing_ids;
|
||||
ListConnections(&erasing_ids);
|
||||
|
||||
for (size_t i = 0; i < erasing_ids.size(); ++i) {
|
||||
SocketUniquePtr socket;
|
||||
if (Socket::Address(erasing_ids[i], &socket) == 0) {
|
||||
if (socket->shall_fail_me_at_server_stop()) {
|
||||
// Mainly streaming connections, should be SetFailed() to
|
||||
// trigger callbacks to NotifyOnFailed() to remove references,
|
||||
// otherwise the sockets are often referenced by corresponding
|
||||
// objects and delay server's stopping which requires all
|
||||
// existing sockets to be recycled.
|
||||
socket->SetFailed(ELOGOFF, "Server is stopping");
|
||||
} else {
|
||||
// Message-oriented RPC connections. Just release the addtional
|
||||
// reference in the socket, which will be recycled when current
|
||||
// requests have been processed.
|
||||
socket->ReleaseAdditionalReference();
|
||||
}
|
||||
} // else: This socket already called `SetFailed' before
|
||||
}
|
||||
}
|
||||
|
||||
int Acceptor::Initialize() {
|
||||
if (_socket_map.init(INITIAL_CONNECTION_CAP) != 0) {
|
||||
LOG(WARNING) << "Fail to initialize FlatMap, size="
|
||||
<< INITIAL_CONNECTION_CAP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: Join() can happen before StopAccept()
|
||||
void Acceptor::Join() {
|
||||
std::unique_lock<butil::Mutex> mu(_map_mutex);
|
||||
if (_status != STOPPING && _status != RUNNING) { // no need to join.
|
||||
return;
|
||||
}
|
||||
// `_listened_fd' will be set to -1 once it has been recycled
|
||||
while (_listened_fd > 0 || !_socket_map.empty()) {
|
||||
_empty_cond.Wait();
|
||||
}
|
||||
const int saved_idle_timeout_sec = _idle_timeout_sec;
|
||||
_idle_timeout_sec = 0;
|
||||
const bthread_t saved_close_idle_tid = _close_idle_tid;
|
||||
mu.unlock();
|
||||
|
||||
// Join the bthread outside lock.
|
||||
if (saved_idle_timeout_sec > 0) {
|
||||
bthread_stop(saved_close_idle_tid);
|
||||
bthread_join(saved_close_idle_tid, NULL);
|
||||
}
|
||||
|
||||
{
|
||||
BAIDU_SCOPED_LOCK(_map_mutex);
|
||||
_status = READY;
|
||||
}
|
||||
}
|
||||
|
||||
size_t Acceptor::ConnectionCount() const {
|
||||
// Notice that _socket_map may be modified concurrently. This actually
|
||||
// assumes that size() is safe to call concurrently.
|
||||
return _socket_map.size();
|
||||
}
|
||||
|
||||
void Acceptor::ListConnections(std::vector<SocketId>* conn_list,
|
||||
size_t max_copied) {
|
||||
if (conn_list == NULL) {
|
||||
LOG(FATAL) << "Param[conn_list] is NULL";
|
||||
return;
|
||||
}
|
||||
conn_list->clear();
|
||||
// Add additional 10(randomly small number) so that even if
|
||||
// ConnectionCount is inaccurate, enough space is reserved
|
||||
conn_list->reserve(ConnectionCount() + 10);
|
||||
|
||||
std::unique_lock<butil::Mutex> mu(_map_mutex);
|
||||
// Copy all the SocketId (protected by mutex) into a temporary
|
||||
// container to avoid dealing with sockets inside the mutex.
|
||||
size_t ntotal = 0;
|
||||
size_t n = 0;
|
||||
for (SocketMap::const_iterator it = _socket_map.begin();
|
||||
it != _socket_map.end(); ++it, ++ntotal) {
|
||||
if (ntotal >= max_copied) {
|
||||
return;
|
||||
}
|
||||
if (++n >= 256/*max iterated one pass*/) {
|
||||
SocketMap::PositionHint hint;
|
||||
_socket_map.save_iterator(it, &hint);
|
||||
n = 0;
|
||||
mu.unlock(); // yield
|
||||
mu.lock();
|
||||
it = _socket_map.restore_iterator(hint);
|
||||
if (it == _socket_map.begin()) { // resized
|
||||
conn_list->clear();
|
||||
}
|
||||
if (it == _socket_map.end()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
conn_list->push_back(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
void Acceptor::ListConnections(std::vector<SocketId>* conn_list) {
|
||||
return ListConnections(conn_list, std::numeric_limits<size_t>::max());
|
||||
}
|
||||
|
||||
void Acceptor::OnNewConnectionsUntilEAGAIN(Socket* acception) {
|
||||
while (1) {
|
||||
struct sockaddr_storage in_addr;
|
||||
bzero(&in_addr, sizeof(in_addr));
|
||||
socklen_t in_len = sizeof(in_addr);
|
||||
butil::fd_guard in_fd(accept(acception->fd(), (sockaddr*)&in_addr, &in_len));
|
||||
if (in_fd < 0) {
|
||||
// no EINTR because listened fd is non-blocking.
|
||||
if (errno == EAGAIN) {
|
||||
return;
|
||||
}
|
||||
// Do NOT return -1 when `accept' failed, otherwise `_listened_fd'
|
||||
// will be closed. Continue to consume all the events until EAGAIN
|
||||
// instead.
|
||||
// If the accept was failed, the error may repeat constantly,
|
||||
// limit frequency of logging.
|
||||
PLOG_EVERY_SECOND(ERROR)
|
||||
<< "Fail to accept from listened_fd=" << acception->fd();
|
||||
continue;
|
||||
}
|
||||
|
||||
Acceptor* am = dynamic_cast<Acceptor*>(acception->user());
|
||||
if (NULL == am) {
|
||||
LOG(FATAL) << "Impossible! acception->user() MUST be Acceptor";
|
||||
acception->SetFailed(EINVAL, "Impossible! acception->user() MUST be Acceptor");
|
||||
return;
|
||||
}
|
||||
|
||||
SocketId socket_id;
|
||||
SocketOptions options;
|
||||
options.keytable_pool = am->_keytable_pool;
|
||||
options.fd = in_fd;
|
||||
butil::sockaddr2endpoint(&in_addr, in_len, &options.remote_side);
|
||||
options.user = acception->user();
|
||||
options.need_on_edge_trigger = true;
|
||||
options.force_ssl = am->_force_ssl;
|
||||
options.initial_ssl_ctx = am->_ssl_ctx;
|
||||
options.socket_mode = am->_socket_mode;
|
||||
options.bthread_tag = am->_bthread_tag;
|
||||
if (Socket::Create(options, &socket_id) != 0) {
|
||||
LOG(ERROR) << "Fail to create Socket";
|
||||
continue;
|
||||
}
|
||||
in_fd.release(); // transfer ownership to socket_id
|
||||
|
||||
// There's a funny race condition here. After Socket::Create, messages
|
||||
// from the socket are already handled and a RPC is possibly done
|
||||
// before the socket is added into _socket_map below. This is found in
|
||||
// ChannelTest.skip_parallel in test/brpc_channel_unittest.cpp (running
|
||||
// on machines with few cores) where the _messenger.ConnectionCount()
|
||||
// may surprisingly be 0 even if the RPC is already done.
|
||||
|
||||
SocketUniquePtr sock;
|
||||
if (Socket::AddressFailedAsWell(socket_id, &sock) >= 0) {
|
||||
bool is_running = true;
|
||||
{
|
||||
BAIDU_SCOPED_LOCK(am->_map_mutex);
|
||||
is_running = (am->status() == RUNNING);
|
||||
// Always add this socket into `_socket_map' whether it
|
||||
// has been `SetFailed' or not, whether `Acceptor' is
|
||||
// running or not. Otherwise, `Acceptor::BeforeRecycle'
|
||||
// may be called (inside Socket::BeforeRecycled) after `Acceptor'
|
||||
// has been destroyed
|
||||
am->_socket_map.insert(socket_id, ConnectStatistics());
|
||||
}
|
||||
if (!is_running) {
|
||||
LOG(WARNING) << "Acceptor on fd=" << acception->fd()
|
||||
<< " has been stopped, discard newly created " << *sock;
|
||||
sock->SetFailed(ELOGOFF, "Acceptor on fd=%d has been stopped, "
|
||||
"discard newly created %s", acception->fd(),
|
||||
sock->description().c_str());
|
||||
return;
|
||||
}
|
||||
} // else: The socket has already been destroyed, Don't add its id
|
||||
// into _socket_map
|
||||
}
|
||||
}
|
||||
|
||||
void Acceptor::OnNewConnections(Socket* acception) {
|
||||
int progress = Socket::PROGRESS_INIT;
|
||||
do {
|
||||
OnNewConnectionsUntilEAGAIN(acception);
|
||||
if (acception->Failed()) {
|
||||
return;
|
||||
}
|
||||
} while (acception->MoreReadEvents(&progress));
|
||||
}
|
||||
|
||||
void Acceptor::BeforeRecycle(Socket* sock) {
|
||||
BAIDU_SCOPED_LOCK(_map_mutex);
|
||||
if (sock->id() == _acception_id) {
|
||||
// Set _listened_fd to -1 when acception socket has been recycled
|
||||
// so that we are ensured no more events will arrive (and `Join'
|
||||
// will return to its caller)
|
||||
_listened_fd = -1;
|
||||
_empty_cond.Broadcast();
|
||||
return;
|
||||
}
|
||||
// If a Socket could not be addressed shortly after its creation, it
|
||||
// was not added into `_socket_map'.
|
||||
_socket_map.erase(sock->id());
|
||||
if (_socket_map.empty()) {
|
||||
_empty_cond.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,124 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_ACCEPTOR_H
|
||||
#define BRPC_ACCEPTOR_H
|
||||
|
||||
#include "bthread/bthread.h" // bthread_t
|
||||
#include "butil/synchronization/condition_variable.h"
|
||||
#include "butil/containers/flat_map.h"
|
||||
#include "brpc/input_messenger.h"
|
||||
#include "brpc/socket_mode.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
struct ConnectStatistics {
|
||||
};
|
||||
|
||||
// Accept connections from a specific port and then
|
||||
// process messages from which it reads
|
||||
class Acceptor : public InputMessenger {
|
||||
friend class Server;
|
||||
public:
|
||||
typedef butil::FlatMap<SocketId, ConnectStatistics> SocketMap;
|
||||
|
||||
enum Status {
|
||||
UNINITIALIZED = 0,
|
||||
READY = 1,
|
||||
RUNNING = 2,
|
||||
STOPPING = 3,
|
||||
};
|
||||
|
||||
public:
|
||||
explicit Acceptor(bthread_keytable_pool_t* pool = NULL);
|
||||
~Acceptor();
|
||||
|
||||
// [thread-safe] Accept connections from `listened_fd'. Ownership of
|
||||
// `listened_fd' is also transferred to `Acceptor'. Can be called
|
||||
// multiple times if the last `StartAccept' has been completely stopped
|
||||
// by calling `StopAccept' and `Join'. Connections that has no data
|
||||
// transmission for `idle_timeout_sec' will be closed automatically iff
|
||||
// `idle_timeout_sec' > 0
|
||||
// Return 0 on success, -1 otherwise.
|
||||
int StartAccept(int listened_fd, int idle_timeout_sec,
|
||||
const std::shared_ptr<SocketSSLContext>& ssl_ctx,
|
||||
bool force_ssl);
|
||||
|
||||
// [thread-safe] Stop accepting connections.
|
||||
// `closewait_ms' is not used anymore.
|
||||
void StopAccept(int /*closewait_ms*/);
|
||||
|
||||
// Wait until all existing Sockets(defined in socket.h) are recycled.
|
||||
void Join();
|
||||
|
||||
// The parameter to StartAccept. Negative when acceptor is stopped.
|
||||
int listened_fd() const { return _listened_fd; }
|
||||
|
||||
// Get number of existing connections.
|
||||
size_t ConnectionCount() const;
|
||||
|
||||
// Clear `conn_list' and append all connections into it.
|
||||
void ListConnections(std::vector<SocketId>* conn_list);
|
||||
|
||||
// Clear `conn_list' and append all most `max_copied' connections into it.
|
||||
void ListConnections(std::vector<SocketId>* conn_list, size_t max_copied);
|
||||
|
||||
Status status() const { return _status; }
|
||||
|
||||
private:
|
||||
// Accept connections.
|
||||
static void OnNewConnectionsUntilEAGAIN(Socket* m);
|
||||
static void OnNewConnections(Socket* m);
|
||||
|
||||
static void* CloseIdleConnections(void* arg);
|
||||
|
||||
// Initialize internal structure.
|
||||
int Initialize();
|
||||
|
||||
// Remove the accepted socket `sock' from inside
|
||||
void BeforeRecycle(Socket* sock) override;
|
||||
|
||||
bthread_keytable_pool_t* _keytable_pool; // owned by Server
|
||||
Status _status;
|
||||
int _idle_timeout_sec;
|
||||
bthread_t _close_idle_tid;
|
||||
|
||||
int _listened_fd;
|
||||
// The Socket tso accept connections.
|
||||
SocketId _acception_id;
|
||||
|
||||
butil::Mutex _map_mutex;
|
||||
butil::ConditionVariable _empty_cond;
|
||||
|
||||
// The map containing all the accepted sockets
|
||||
SocketMap _socket_map;
|
||||
|
||||
bool _force_ssl;
|
||||
std::shared_ptr<SocketSSLContext> _ssl_ctx;
|
||||
|
||||
// Choose to use a certain socket: 0 TCP, 1 RDMA
|
||||
SocketMode _socket_mode;
|
||||
|
||||
// Acceptor belongs to this tag
|
||||
bthread_tag_t _bthread_tag;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_ACCEPTOR_H
|
||||
@@ -0,0 +1,72 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include "butil/logging.h"
|
||||
#include "brpc/adaptive_connection_type.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
inline bool CompareStringPieceWithoutCase(
|
||||
const butil::StringPiece& s1, const char* s2) {
|
||||
if (strlen(s2) != s1.size()) {
|
||||
return false;
|
||||
}
|
||||
return strncasecmp(s1.data(), s2, s1.size()) == 0;
|
||||
}
|
||||
|
||||
ConnectionType StringToConnectionType(const butil::StringPiece& type,
|
||||
bool print_log_on_unknown) {
|
||||
if (CompareStringPieceWithoutCase(type, "single")) {
|
||||
return CONNECTION_TYPE_SINGLE;
|
||||
} else if (CompareStringPieceWithoutCase(type, "pooled")) {
|
||||
return CONNECTION_TYPE_POOLED;
|
||||
} else if (CompareStringPieceWithoutCase(type, "short")) {
|
||||
return CONNECTION_TYPE_SHORT;
|
||||
}
|
||||
LOG_IF(ERROR, print_log_on_unknown && !type.empty())
|
||||
<< "Unknown connection_type `" << type
|
||||
<< "', supported types: single pooled short";
|
||||
return CONNECTION_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
const char* ConnectionTypeToString(ConnectionType type) {
|
||||
switch (type) {
|
||||
case CONNECTION_TYPE_UNKNOWN:
|
||||
return "unknown";
|
||||
case CONNECTION_TYPE_SINGLE:
|
||||
return "single";
|
||||
case CONNECTION_TYPE_POOLED:
|
||||
return "pooled";
|
||||
case CONNECTION_TYPE_SHORT:
|
||||
return "short";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
void AdaptiveConnectionType::operator=(const butil::StringPiece& name) {
|
||||
if (name.empty()) {
|
||||
_type = CONNECTION_TYPE_UNKNOWN;
|
||||
_error = false;
|
||||
} else {
|
||||
_type = StringToConnectionType(name);
|
||||
_error = (_type == CONNECTION_TYPE_UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,70 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_ADAPTIVE_CONNECTION_TYPE_H
|
||||
#define BRPC_ADAPTIVE_CONNECTION_TYPE_H
|
||||
|
||||
// To brpc developers: This is a header included by user, don't depend
|
||||
// on internal structures, use opaque pointers instead.
|
||||
|
||||
#include "butil/strings/string_piece.h"
|
||||
#include "brpc/options.pb.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Convert a case-insensitive string to corresponding ConnectionType
|
||||
// Possible options are: short, pooled, single
|
||||
// Returns: CONNECTION_TYPE_UNKNOWN on error.
|
||||
ConnectionType StringToConnectionType(const butil::StringPiece& type,
|
||||
bool print_log_on_unknown);
|
||||
inline ConnectionType StringToConnectionType(const butil::StringPiece& type)
|
||||
{ return StringToConnectionType(type, true); }
|
||||
|
||||
// Convert a ConnectionType to a c-style string.
|
||||
const char* ConnectionTypeToString(ConnectionType);
|
||||
|
||||
// Assignable by both ConnectionType and names.
|
||||
class AdaptiveConnectionType {
|
||||
public:
|
||||
AdaptiveConnectionType() : _type(CONNECTION_TYPE_UNKNOWN), _error(false) {}
|
||||
AdaptiveConnectionType(ConnectionType type) : _type(type), _error(false) {}
|
||||
~AdaptiveConnectionType() {}
|
||||
|
||||
void operator=(ConnectionType type) {
|
||||
_type = type;
|
||||
_error = false;
|
||||
}
|
||||
void operator=(const butil::StringPiece& name);
|
||||
|
||||
operator ConnectionType() const { return _type; }
|
||||
const char* name() const { return ConnectionTypeToString(_type); }
|
||||
bool has_error() const { return _error; }
|
||||
|
||||
private:
|
||||
ConnectionType _type;
|
||||
// Since this structure occupies 8 bytes in 64-bit machines anyway,
|
||||
// we add a field to mark if last operator=(name) failed so that
|
||||
// channel can print a error log before re-selecting a valid
|
||||
// ConnectionType for user.
|
||||
bool _error;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_ADAPTIVE_CONNECTION_TYPE_H
|
||||
@@ -0,0 +1,119 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#include <cstring>
|
||||
#include <strings.h>
|
||||
#include "butil/string_printf.h"
|
||||
#include "butil/logging.h"
|
||||
#include "butil/strings/string_number_conversions.h"
|
||||
#include "brpc/adaptive_max_concurrency.h"
|
||||
#include "brpc/concurrency_limiter.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
AdaptiveMaxConcurrency::AdaptiveMaxConcurrency()
|
||||
: _value(UNLIMITED())
|
||||
, _max_concurrency(0) {
|
||||
}
|
||||
|
||||
AdaptiveMaxConcurrency::AdaptiveMaxConcurrency(int max_concurrency)
|
||||
: _max_concurrency(0) {
|
||||
if (max_concurrency <= 0) {
|
||||
_value = UNLIMITED();
|
||||
_max_concurrency = 0;
|
||||
} else {
|
||||
_value = butil::string_printf("%d", max_concurrency);
|
||||
_max_concurrency = max_concurrency;
|
||||
}
|
||||
}
|
||||
|
||||
AdaptiveMaxConcurrency::AdaptiveMaxConcurrency(
|
||||
const TimeoutConcurrencyConf& value)
|
||||
: _value("timeout"), _max_concurrency(-1), _timeout_conf(value) {}
|
||||
|
||||
inline bool CompareStringPieceWithoutCase(
|
||||
const butil::StringPiece& s1, const char* s2) {
|
||||
DCHECK(s2 != NULL);
|
||||
if (std::strlen(s2) != s1.size()) {
|
||||
return false;
|
||||
}
|
||||
return ::strncasecmp(s1.data(), s2, s1.size()) == 0;
|
||||
}
|
||||
|
||||
AdaptiveMaxConcurrency::AdaptiveMaxConcurrency(const butil::StringPiece& value)
|
||||
: _max_concurrency(0) {
|
||||
int max_concurrency = 0;
|
||||
if (butil::StringToInt(value, &max_concurrency)) {
|
||||
operator=(max_concurrency);
|
||||
} else {
|
||||
value.CopyToString(&_value);
|
||||
_max_concurrency = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void AdaptiveMaxConcurrency::operator=(const butil::StringPiece& value) {
|
||||
int max_concurrency = 0;
|
||||
if (butil::StringToInt(value, &max_concurrency)) {
|
||||
return operator=(max_concurrency);
|
||||
} else {
|
||||
value.CopyToString(&_value);
|
||||
_max_concurrency = -1;
|
||||
}
|
||||
if (_cl) {
|
||||
_cl->ResetMaxConcurrency(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void AdaptiveMaxConcurrency::operator=(int max_concurrency) {
|
||||
if (max_concurrency <= 0) {
|
||||
_value = UNLIMITED();
|
||||
_max_concurrency = 0;
|
||||
} else {
|
||||
_value = butil::string_printf("%d", max_concurrency);
|
||||
_max_concurrency = max_concurrency;
|
||||
}
|
||||
if (_cl) {
|
||||
_cl->ResetMaxConcurrency(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void AdaptiveMaxConcurrency::operator=(const TimeoutConcurrencyConf& value) {
|
||||
_value = "timeout";
|
||||
_max_concurrency = -1;
|
||||
_timeout_conf = value;
|
||||
if (_cl) {
|
||||
_cl->ResetMaxConcurrency(*this);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& AdaptiveMaxConcurrency::type() const {
|
||||
if (_max_concurrency > 0) {
|
||||
return CONSTANT();
|
||||
} else if (_max_concurrency == 0) {
|
||||
return UNLIMITED();
|
||||
} else {
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const AdaptiveMaxConcurrency& adaptive_concurrency,
|
||||
const butil::StringPiece& concurrency) {
|
||||
return CompareStringPieceWithoutCase(concurrency,
|
||||
adaptive_concurrency.value().c_str());
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,115 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_ADAPTIVE_MAX_CONCURRENCY_H
|
||||
#define BRPC_ADAPTIVE_MAX_CONCURRENCY_H
|
||||
|
||||
// To brpc developers: This is a header included by user, don't depend
|
||||
// on internal structures, use opaque pointers instead.
|
||||
|
||||
#include "butil/strings/string_piece.h"
|
||||
#include "brpc/options.pb.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// timeout concurrency limiter config
|
||||
struct TimeoutConcurrencyConf {
|
||||
int64_t timeout_ms;
|
||||
int max_concurrency;
|
||||
};
|
||||
|
||||
class ConcurrencyLimiter;
|
||||
class AdaptiveMaxConcurrency{
|
||||
public:
|
||||
explicit AdaptiveMaxConcurrency();
|
||||
explicit AdaptiveMaxConcurrency(int max_concurrency);
|
||||
explicit AdaptiveMaxConcurrency(const butil::StringPiece& value);
|
||||
explicit AdaptiveMaxConcurrency(const TimeoutConcurrencyConf& value);
|
||||
|
||||
// Non-trivial destructor to prevent AdaptiveMaxConcurrency from being
|
||||
// passed to variadic arguments without explicit type conversion.
|
||||
// eg:
|
||||
// printf("%d", options.max_concurrency) // compile error
|
||||
// printf("%s", options.max_concurrency.value().c_str()) // ok
|
||||
~AdaptiveMaxConcurrency() {}
|
||||
|
||||
void operator=(int max_concurrency);
|
||||
void operator=(const butil::StringPiece& value);
|
||||
void operator=(const TimeoutConcurrencyConf& value);
|
||||
|
||||
// 0 for type="unlimited"
|
||||
// >0 for type="constant"
|
||||
// <0 for type="user-defined"
|
||||
operator int() const { return _max_concurrency; }
|
||||
operator TimeoutConcurrencyConf() const { return _timeout_conf; }
|
||||
|
||||
// "unlimited" for type="unlimited"
|
||||
// "10" "20" "30" for type="constant"
|
||||
// "user-defined" for type="user-defined"
|
||||
const std::string& value() const { return _value; }
|
||||
|
||||
// "unlimited", "constant" or "user-defined"
|
||||
const std::string& type() const;
|
||||
|
||||
// Use Meyers' Singleton to avoid static initialization order fiasco:
|
||||
// global AdaptiveMaxConcurrency objects in other translation units may
|
||||
// depend on these strings during their construction.
|
||||
static const std::string& UNLIMITED() {
|
||||
static const std::string s_unlimited = "unlimited";
|
||||
return s_unlimited;
|
||||
}
|
||||
static const std::string& CONSTANT() {
|
||||
static const std::string s_constant = "constant";
|
||||
return s_constant;
|
||||
}
|
||||
|
||||
void SetConcurrencyLimiter(ConcurrencyLimiter* cl) { _cl = cl; }
|
||||
|
||||
private:
|
||||
std::string _value;
|
||||
int _max_concurrency;
|
||||
TimeoutConcurrencyConf
|
||||
_timeout_conf; // TODO std::varient for different type
|
||||
ConcurrencyLimiter* _cl{nullptr};
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const AdaptiveMaxConcurrency& amc) {
|
||||
return os << amc.value();
|
||||
}
|
||||
|
||||
bool operator==(const AdaptiveMaxConcurrency& adaptive_concurrency,
|
||||
const butil::StringPiece& concurrency);
|
||||
|
||||
inline bool operator==(const butil::StringPiece& concurrency,
|
||||
const AdaptiveMaxConcurrency& adaptive_concurrency) {
|
||||
return adaptive_concurrency == concurrency;
|
||||
}
|
||||
|
||||
inline bool operator!=(const AdaptiveMaxConcurrency& adaptive_concurrency,
|
||||
const butil::StringPiece& concurrency) {
|
||||
return !(adaptive_concurrency == concurrency);
|
||||
}
|
||||
|
||||
inline bool operator!=(const butil::StringPiece& concurrency,
|
||||
const AdaptiveMaxConcurrency& adaptive_concurrency) {
|
||||
return !(adaptive_concurrency == concurrency);
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_ADAPTIVE_MAX_CONCURRENCY_H
|
||||
@@ -0,0 +1,93 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_ADAPTIVE_PROTOCOL_TYPE_H
|
||||
#define BRPC_ADAPTIVE_PROTOCOL_TYPE_H
|
||||
|
||||
// To brpc developers: This is a header included by user, don't depend
|
||||
// on internal structures, use opaque pointers instead.
|
||||
|
||||
#include "butil/strings/string_piece.h"
|
||||
#include "brpc/options.pb.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// NOTE: impl. are in brpc/protocol.cpp
|
||||
|
||||
// Convert a case-insensitive string to corresponding ProtocolType which is
|
||||
// defined in src/brpc/options.proto
|
||||
// Returns: PROTOCOL_UNKNOWN on error.
|
||||
ProtocolType StringToProtocolType(const butil::StringPiece& type,
|
||||
bool print_log_on_unknown);
|
||||
inline ProtocolType StringToProtocolType(const butil::StringPiece& type)
|
||||
{ return StringToProtocolType(type, true); }
|
||||
|
||||
// Convert a ProtocolType to a c-style string.
|
||||
const char* ProtocolTypeToString(ProtocolType);
|
||||
|
||||
// Assignable by both ProtocolType and names.
|
||||
class AdaptiveProtocolType {
|
||||
public:
|
||||
explicit AdaptiveProtocolType() : _type(PROTOCOL_UNKNOWN) {}
|
||||
explicit AdaptiveProtocolType(ProtocolType type) : _type(type) {}
|
||||
explicit AdaptiveProtocolType(butil::StringPiece name) { *this = name; }
|
||||
~AdaptiveProtocolType() {}
|
||||
|
||||
void operator=(ProtocolType type) {
|
||||
_type = type;
|
||||
_name.clear();
|
||||
_param.clear();
|
||||
}
|
||||
|
||||
void operator=(butil::StringPiece name) {
|
||||
butil::StringPiece param;
|
||||
const size_t pos = name.find(':');
|
||||
if (pos != butil::StringPiece::npos) {
|
||||
param = name.substr(pos + 1);
|
||||
name.remove_suffix(name.size() - pos);
|
||||
}
|
||||
_type = StringToProtocolType(name);
|
||||
if (_type == PROTOCOL_UNKNOWN) {
|
||||
_name.assign(name.data(), name.size());
|
||||
} else {
|
||||
_name.clear();
|
||||
}
|
||||
if (!param.empty()) {
|
||||
_param.assign(param.data(), param.size());
|
||||
} else {
|
||||
_param.clear();
|
||||
}
|
||||
};
|
||||
|
||||
operator ProtocolType() const { return _type; }
|
||||
|
||||
const char* name() const {
|
||||
return _name.empty() ? ProtocolTypeToString(_type) : _name.c_str();
|
||||
}
|
||||
|
||||
bool has_param() const { return !_param.empty(); }
|
||||
const std::string& param() const { return _param; }
|
||||
|
||||
private:
|
||||
ProtocolType _type;
|
||||
std::string _name;
|
||||
std::string _param;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif // BRPC_ADAPTIVE_PROTOCOL_TYPE_H
|
||||
+1296
File diff suppressed because it is too large
Load Diff
+304
@@ -0,0 +1,304 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_AMF_H
|
||||
#define BRPC_AMF_H
|
||||
|
||||
#include <map>
|
||||
#include <deque>
|
||||
#include <google/protobuf/io/zero_copy_stream.h>
|
||||
#include <google/protobuf/message.h>
|
||||
#include "butil/sys_byteorder.h"
|
||||
#include "butil/strings/string_piece.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// For parsing and serializing Action Message Format used throughout RTMP.
|
||||
|
||||
// Buffer ZeroCopyInputStream as efficient input of parsing AMF.
|
||||
class AMFInputStream {
|
||||
public:
|
||||
AMFInputStream(google::protobuf::io::ZeroCopyInputStream* stream)
|
||||
: _good(true)
|
||||
, _size(0)
|
||||
, _data(NULL)
|
||||
, _zc_stream(stream)
|
||||
, _popped_bytes(0)
|
||||
{}
|
||||
|
||||
~AMFInputStream() { }
|
||||
|
||||
// Cut off at-most n bytes from front side and copy to `out'.
|
||||
// Returns bytes cut.
|
||||
size_t cutn(void* out, size_t n);
|
||||
|
||||
size_t cut_u8(uint8_t* u16);
|
||||
size_t cut_u16(uint16_t* u16);
|
||||
size_t cut_u32(uint32_t* u32);
|
||||
size_t cut_u64(uint64_t* u64);
|
||||
|
||||
// Returns bytes popped and cut since creation of this stream.
|
||||
size_t popped_bytes() const { return _popped_bytes; }
|
||||
|
||||
// Returns false if error occurred in other consuming functions.
|
||||
bool good() const { return _good; }
|
||||
|
||||
// If the error prevents parsing from going on, call this method.
|
||||
// This method is also called in other functions in this class.
|
||||
void set_bad() { _good = false; }
|
||||
|
||||
// Return true if the stream is empty. Notice that this function
|
||||
// may update _data and _size.
|
||||
bool check_emptiness();
|
||||
|
||||
private:
|
||||
bool _good;
|
||||
int _size;
|
||||
const void* _data;
|
||||
google::protobuf::io::ZeroCopyInputStream* _zc_stream;
|
||||
size_t _popped_bytes;
|
||||
};
|
||||
|
||||
// Buffer serialize data of AMF to ZeroCopyOutputStream
|
||||
class AMFOutputStream {
|
||||
public:
|
||||
AMFOutputStream(google::protobuf::io::ZeroCopyOutputStream* stream)
|
||||
: _good(true)
|
||||
, _size(0)
|
||||
, _data(NULL)
|
||||
, _zc_stream(stream)
|
||||
, _pushed_bytes(0)
|
||||
{}
|
||||
|
||||
~AMFOutputStream() { done(); }
|
||||
|
||||
// Append n bytes.
|
||||
void putn(const void* data, int n);
|
||||
|
||||
void put_u8(uint8_t u16);
|
||||
void put_u16(uint16_t u16);
|
||||
void put_u32(uint32_t u32);
|
||||
void put_u64(uint64_t u64);
|
||||
|
||||
// Returns bytes pushed and cut since creation of this stream.
|
||||
size_t pushed_bytes() const { return _pushed_bytes; }
|
||||
|
||||
// Returns false if error occurred during serialization.
|
||||
bool good() { return _good; }
|
||||
|
||||
void set_bad() { _good = false; }
|
||||
|
||||
// Optionally called to backup buffered bytes to zero-copy stream.
|
||||
void done();
|
||||
|
||||
private:
|
||||
bool _good;
|
||||
int _size;
|
||||
void* _data;
|
||||
google::protobuf::io::ZeroCopyOutputStream* _zc_stream;
|
||||
size_t _pushed_bytes;
|
||||
};
|
||||
|
||||
// There are 16 core type markers in AMF 0. A type marker is one byte in
|
||||
// length and describes the kind of encoded data that may follow.
|
||||
enum AMFMarker {
|
||||
AMF_MARKER_NUMBER = 0x00,
|
||||
AMF_MARKER_BOOLEAN = 0x01,
|
||||
AMF_MARKER_STRING = 0x02,
|
||||
AMF_MARKER_OBJECT = 0x03,
|
||||
AMF_MARKER_MOVIECLIP = 0x04,
|
||||
AMF_MARKER_NULL = 0x05,
|
||||
AMF_MARKER_UNDEFINED = 0x06,
|
||||
AMF_MARKER_REFERENCE = 0x07,
|
||||
AMF_MARKER_ECMA_ARRAY = 0x08,
|
||||
AMF_MARKER_OBJECT_END = 0x09,
|
||||
AMF_MARKER_STRICT_ARRAY = 0x0A,
|
||||
AMF_MARKER_DATE = 0x0B,
|
||||
AMF_MARKER_LONG_STRING = 0x0C,
|
||||
AMF_MARKER_UNSUPPORTED = 0x0D,
|
||||
AMF_MARKER_RECORDSET = 0x0E,
|
||||
AMF_MARKER_XML_DOCUMENT = 0x0F,
|
||||
AMF_MARKER_TYPED_OBJECT = 0x10,
|
||||
AMF_MARKER_AVMPLUS_OBJECT = 0x11
|
||||
};
|
||||
|
||||
const char* marker2str(AMFMarker marker);
|
||||
const char* marker2str(uint8_t marker);
|
||||
|
||||
class AMFObject;
|
||||
class AMFArray;
|
||||
|
||||
// A field inside a AMF object.
|
||||
class AMFField {
|
||||
friend class AMFObject;
|
||||
public:
|
||||
static const size_t SSO_LIMIT = 8;
|
||||
|
||||
AMFField();
|
||||
AMFField(const AMFField&);
|
||||
AMFField& operator=(const AMFField&);
|
||||
~AMFField() { Clear(); }
|
||||
void Clear() { if (_type != AMF_MARKER_UNDEFINED) { SlowerClear(); } }
|
||||
|
||||
AMFMarker type() const { return (AMFMarker)_type; }
|
||||
|
||||
bool IsString() const
|
||||
{ return _type == AMF_MARKER_STRING || _type == AMF_MARKER_LONG_STRING; }
|
||||
bool IsBool() const { return _type == AMF_MARKER_BOOLEAN; }
|
||||
bool IsNumber() const { return _type == AMF_MARKER_NUMBER; }
|
||||
bool IsObject() const
|
||||
{ return _type == AMF_MARKER_OBJECT || _type == AMF_MARKER_ECMA_ARRAY; }
|
||||
bool IsArray() const { return _type == AMF_MARKER_STRICT_ARRAY; }
|
||||
|
||||
butil::StringPiece AsString() const
|
||||
{ return butil::StringPiece((_is_shortstr ? _shortstr : _str), _strsize); }
|
||||
bool AsBool() const { return _b; }
|
||||
double AsNumber() const { return _num; }
|
||||
const AMFObject& AsObject() const { return *_obj; }
|
||||
const AMFArray& AsArray() const { return *_arr; }
|
||||
|
||||
void SetString(const butil::StringPiece& str);
|
||||
void SetBool(bool val);
|
||||
void SetNumber(double val);
|
||||
void SetNull();
|
||||
void SetUndefined();
|
||||
void SetUnsupported();
|
||||
AMFObject* MutableObject();
|
||||
AMFArray* MutableArray();
|
||||
|
||||
private:
|
||||
void SlowerClear();
|
||||
|
||||
uint8_t _type;
|
||||
bool _is_shortstr;
|
||||
uint32_t _strsize;
|
||||
union {
|
||||
double _num;
|
||||
char* _str;
|
||||
char _shortstr[SSO_LIMIT]; // SSO
|
||||
bool _b;
|
||||
AMFObject* _obj;
|
||||
AMFArray* _arr;
|
||||
};
|
||||
};
|
||||
std::ostream& operator<<(std::ostream& os, const AMFField& field);
|
||||
|
||||
// A general AMF object.
|
||||
class AMFObject {
|
||||
public:
|
||||
typedef std::map<std::string, AMFField>::iterator iterator;
|
||||
typedef std::map<std::string, AMFField>::const_iterator const_iterator;
|
||||
|
||||
const AMFField* Find(const char* name) const;
|
||||
void Remove(const std::string& name) { _fields.erase(name); }
|
||||
void Clear() { _fields.clear(); }
|
||||
|
||||
void SetString(const std::string& name, const butil::StringPiece& val);
|
||||
void SetBool(const std::string& name, bool val);
|
||||
void SetNumber(const std::string& name, double val);
|
||||
void SetNull(const std::string& name);
|
||||
void SetUndefined(const std::string& name);
|
||||
void SetUnsupported(const std::string& name);
|
||||
AMFObject* MutableObject(const std::string& name);
|
||||
AMFArray* MutableArray(const std::string& name);
|
||||
|
||||
iterator begin() { return _fields.begin(); }
|
||||
const_iterator begin() const { return _fields.begin(); }
|
||||
iterator end() { return _fields.end(); }
|
||||
const_iterator end() const { return _fields.end(); }
|
||||
|
||||
private:
|
||||
std::map<std::string, AMFField> _fields;
|
||||
};
|
||||
std::ostream& operator<<(std::ostream& os, const AMFObject&);
|
||||
|
||||
// An AMF strict array (not ecma array)
|
||||
class AMFArray {
|
||||
public:
|
||||
AMFArray();
|
||||
AMFArray(const AMFArray&);
|
||||
AMFArray& operator=(const AMFArray&);
|
||||
~AMFArray() { Clear(); }
|
||||
void Clear();
|
||||
|
||||
const AMFField& operator[](size_t index) const;
|
||||
AMFField& operator[](size_t index);
|
||||
size_t size() const { return _size; }
|
||||
|
||||
void AddString(const butil::StringPiece& val) { AddField()->SetString(val); }
|
||||
void AddBool(bool val) { AddField()->SetBool(val); }
|
||||
void AddNumber(double val) { AddField()->SetNumber(val); }
|
||||
void AddNull() { AddField()->SetNull(); }
|
||||
void AddUndefined() { AddField()->SetUndefined(); }
|
||||
void AddUnsupported() { AddField()->SetUnsupported(); }
|
||||
AMFObject* AddObject() { return AddField()->MutableObject(); }
|
||||
AMFArray* AddArray() { return AddField()->MutableArray(); }
|
||||
|
||||
private:
|
||||
AMFField* AddField();
|
||||
void RemoveLastField();
|
||||
|
||||
uint32_t _size;
|
||||
AMFField _fields[4];
|
||||
std::deque<AMFField> _morefields;
|
||||
};
|
||||
std::ostream& operator<<(std::ostream& os, const AMFArray&);
|
||||
|
||||
inline const AMFField& AMFArray::operator[](size_t index) const {
|
||||
return (index < arraysize(_fields) ? _fields[index] :
|
||||
_morefields[index - arraysize(_fields)]);
|
||||
}
|
||||
inline AMFField& AMFArray::operator[](size_t index) {
|
||||
return (index < arraysize(_fields) ? _fields[index] :
|
||||
_morefields[index - arraysize(_fields)]);
|
||||
}
|
||||
|
||||
// Parse types of the stream.
|
||||
bool ReadAMFString(std::string* val, AMFInputStream* stream);
|
||||
bool ReadAMFBool(bool* val, AMFInputStream* stream);
|
||||
bool ReadAMFNumber(double* val, AMFInputStream* stream);
|
||||
bool ReadAMFUint32(uint32_t* val, AMFInputStream* stream);
|
||||
bool ReadAMFNull(AMFInputStream* stream);
|
||||
bool ReadAMFUndefined(AMFInputStream* stream);
|
||||
bool ReadAMFUnsupported(AMFInputStream* stream);
|
||||
// The pb version just loads known fields (defined in proto)
|
||||
bool ReadAMFObject(google::protobuf::Message* msg, AMFInputStream* stream);
|
||||
bool ReadAMFObject(AMFObject* obj, AMFInputStream* stream);
|
||||
bool ReadAMFArray(AMFArray* arr, AMFInputStream* stream);
|
||||
|
||||
// Serialize types into the stream.
|
||||
// Check stream->good() for successfulness after one or multiple WriteAMFxxx.
|
||||
void WriteAMFString(const butil::StringPiece& val, AMFOutputStream* stream);
|
||||
void WriteAMFBool(bool val, AMFOutputStream* stream);
|
||||
void WriteAMFNumber(double val, AMFOutputStream* stream);
|
||||
void WriteAMFUint32(uint32_t val, AMFOutputStream* stream);
|
||||
void WriteAMFNull(AMFOutputStream* stream);
|
||||
void WriteAMFUndefined(AMFOutputStream* stream);
|
||||
void WriteAMFUnsupported(AMFOutputStream* stream);
|
||||
void WriteAMFObject(const google::protobuf::Message& msg,
|
||||
AMFOutputStream* stream);
|
||||
void WriteAMFObject(const AMFObject& obj, AMFOutputStream* stream);
|
||||
void WriteAMFArray(const AMFArray& arr, AMFOutputStream* stream);
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#include "brpc/amf_inl.h"
|
||||
|
||||
#endif // BRPC_AMF_H
|
||||
@@ -0,0 +1,171 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_AMF_INL_H
|
||||
#define BRPC_AMF_INL_H
|
||||
|
||||
void* fast_memcpy(void *__restrict dest, const void *__restrict src, size_t n);
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
inline size_t AMFInputStream::cutn(void* out, size_t n) {
|
||||
const size_t saved_n = n;
|
||||
do {
|
||||
if (_size >= (int64_t)n) {
|
||||
memcpy(out, _data, n);
|
||||
_data = (const char*)_data + n;
|
||||
_size -= n;
|
||||
_popped_bytes += saved_n;
|
||||
return saved_n;
|
||||
}
|
||||
if (_size) {
|
||||
memcpy(out, _data, _size);
|
||||
out = (char*)out + _size;
|
||||
n -= _size;
|
||||
}
|
||||
} while (_zc_stream->Next(&_data, &_size));
|
||||
_data = NULL;
|
||||
_size = 0;
|
||||
_popped_bytes += saved_n - n;
|
||||
return saved_n - n;
|
||||
}
|
||||
|
||||
inline bool AMFInputStream::check_emptiness() {
|
||||
return _size == 0 && !_zc_stream->Next(&_data, &_size);
|
||||
}
|
||||
|
||||
inline size_t AMFInputStream::cut_u8(uint8_t* val) {
|
||||
if (_size >= 1) {
|
||||
*val = *(uint8_t*)_data;
|
||||
_data = (const char*)_data + 1;
|
||||
_size -= 1;
|
||||
_popped_bytes += 1;
|
||||
return 1;
|
||||
}
|
||||
return cutn(val, 1);
|
||||
}
|
||||
|
||||
inline size_t AMFInputStream::cut_u16(uint16_t* val) {
|
||||
if (_size >= 2) {
|
||||
const uint16_t netval = *(uint16_t*)_data;
|
||||
*val = butil::NetToHost16(netval);
|
||||
_data = (const char*)_data + 2;
|
||||
_size -= 2;
|
||||
_popped_bytes += 2;
|
||||
return 2;
|
||||
}
|
||||
uint16_t netval = 0;
|
||||
const size_t ret = cutn(&netval, 2);
|
||||
*val = butil::NetToHost16(netval);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline size_t AMFInputStream::cut_u32(uint32_t* val) {
|
||||
if (_size >= 4) {
|
||||
const uint32_t netval = *(uint32_t*)_data;
|
||||
*val = butil::NetToHost32(netval);
|
||||
_data = (const char*)_data + 4;
|
||||
_size -= 4;
|
||||
_popped_bytes += 4;
|
||||
return 4;
|
||||
}
|
||||
uint32_t netval = 0;
|
||||
const size_t ret = cutn(&netval, 4);
|
||||
*val = butil::NetToHost32(netval);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline size_t AMFInputStream::cut_u64(uint64_t* val) {
|
||||
if (_size >= 8) {
|
||||
const uint64_t netval = *(uint64_t*)_data;
|
||||
*val = butil::NetToHost64(netval);
|
||||
_data = (const char*)_data + 8;
|
||||
_size -= 8;
|
||||
_popped_bytes += 8;
|
||||
return 8;
|
||||
}
|
||||
uint64_t netval = 0;
|
||||
const size_t ret = cutn(&netval, 8);
|
||||
*val = butil::NetToHost64(netval);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline void AMFOutputStream::done() {
|
||||
if (_good && _size) {
|
||||
_zc_stream->BackUp(_size);
|
||||
_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void AMFOutputStream::putn(const void* data, int n) {
|
||||
const int saved_n = n;
|
||||
do {
|
||||
if (n <= _size) {
|
||||
fast_memcpy(_data, data, n);
|
||||
_data = (char*)_data + n;
|
||||
_size -= n;
|
||||
_pushed_bytes += saved_n;
|
||||
return;
|
||||
}
|
||||
fast_memcpy(_data, data, _size);
|
||||
data = (const char*)data + _size;
|
||||
n -= _size;
|
||||
} while (_zc_stream->Next(&_data, &_size));
|
||||
_data = NULL;
|
||||
_size = 0;
|
||||
_pushed_bytes += (saved_n - n);
|
||||
if (n) {
|
||||
set_bad();
|
||||
}
|
||||
}
|
||||
|
||||
inline void AMFOutputStream::put_u8(uint8_t val) {
|
||||
do {
|
||||
if (_size > 0) {
|
||||
*(uint8_t*)_data = val;
|
||||
_data = (char*)_data + 1;
|
||||
--_size;
|
||||
++_pushed_bytes;
|
||||
return;
|
||||
}
|
||||
} while (_zc_stream->Next(&_data, &_size));
|
||||
_data = NULL;
|
||||
_size = 0;
|
||||
set_bad();
|
||||
}
|
||||
|
||||
inline void AMFOutputStream::put_u16(uint16_t val) {
|
||||
uint16_t netval = butil::HostToNet16(val);
|
||||
return putn(&netval, sizeof(netval));
|
||||
}
|
||||
|
||||
inline void AMFOutputStream::put_u32(uint32_t val) {
|
||||
uint32_t netval = butil::HostToNet32(val);
|
||||
return putn(&netval, sizeof(netval));
|
||||
}
|
||||
|
||||
inline void AMFOutputStream::put_u64(uint64_t val) {
|
||||
uint64_t netval = butil::HostToNet64(val);
|
||||
return putn(&netval, sizeof(netval));
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_AMF_INL_H
|
||||
@@ -0,0 +1,98 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_AUTHENTICATOR_H
|
||||
#define BRPC_AUTHENTICATOR_H
|
||||
|
||||
#include <ostream>
|
||||
#include "butil/endpoint.h" // butil::EndPoint
|
||||
#include "butil/macros.h" // BAIDU_CONCAT
|
||||
#include "brpc/extension.h" // Extension<T>
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class AuthContext {
|
||||
public:
|
||||
AuthContext() : _is_service(false) {}
|
||||
~AuthContext() {}
|
||||
|
||||
const std::string& user() const { return _user; }
|
||||
void set_user(const std::string& user) { _user = user; }
|
||||
|
||||
const std::string& group() const { return _group; }
|
||||
void set_group(const std::string& group) { _group = group; }
|
||||
|
||||
const std::string& roles() const { return _roles; }
|
||||
void set_roles(const std::string& roles) { _roles = roles; }
|
||||
|
||||
const std::string& starter() const { return _starter; }
|
||||
void set_starter(const std::string& starter) { _starter = starter; }
|
||||
|
||||
bool is_service() const { return _is_service; }
|
||||
void set_is_service(bool is_service) { _is_service = is_service; }
|
||||
|
||||
private:
|
||||
bool _is_service;
|
||||
std::string _user;
|
||||
std::string _group;
|
||||
std::string _roles;
|
||||
std::string _starter;
|
||||
};
|
||||
|
||||
class Authenticator {
|
||||
public:
|
||||
virtual ~Authenticator() = default;
|
||||
|
||||
// Implement this method to generate credential information
|
||||
// into `auth_str' which will be sent to `VerifyCredential'
|
||||
// at server side. This method will be called on client side.
|
||||
// Returns 0 on success, error code otherwise
|
||||
virtual int GenerateCredential(std::string* auth_str) const = 0;
|
||||
|
||||
// Implement this method to verify credential information
|
||||
// `auth_str' from `client_addr'. You can fill credential
|
||||
// context (result) into `*out_ctx' and later fetch this
|
||||
// pointer from `Controller'.
|
||||
// Returns 0 on success, error code otherwise
|
||||
virtual int VerifyCredential(const std::string& auth_str,
|
||||
const butil::EndPoint& client_addr,
|
||||
AuthContext* out_ctx) const = 0;
|
||||
|
||||
// Implement this method to unauthorized error text which
|
||||
// will be sent as a part of error text in baidu_std/hulu_pbrpc
|
||||
// protocol or body in http protocol.
|
||||
virtual std::string GetUnauthorizedErrorText() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const AuthContext& ctx) {
|
||||
return os << "[name=" << ctx.user() << " [This is a "
|
||||
<< (ctx.is_service() ? "service" : "user")
|
||||
<< "], group=" << ctx.group() << ", roles=" << ctx.roles()
|
||||
<< ", starter=" << ctx.starter() << "]";
|
||||
}
|
||||
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
|
||||
#endif // BRPC_AUTHENTICATOR_H
|
||||
@@ -0,0 +1,177 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#include "brpc/backup_request_policy.h"
|
||||
|
||||
#include "butil/logging.h"
|
||||
#include "bvar/reducer.h"
|
||||
#include "bvar/window.h"
|
||||
#include "butil/atomicops.h"
|
||||
#include "butil/time.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Standalone statistics module for tracking backup/total request ratio
|
||||
// within a sliding time window. Each instance schedules two bvar::Window
|
||||
// sampler tasks; keep this in mind for high channel-count deployments.
|
||||
class BackupRateLimiter {
|
||||
public:
|
||||
BackupRateLimiter(double max_backup_ratio,
|
||||
int window_size_seconds,
|
||||
int update_interval_seconds)
|
||||
: _max_backup_ratio(max_backup_ratio)
|
||||
, _update_interval_us(update_interval_seconds * 1000000LL)
|
||||
, _total_count()
|
||||
, _backup_count()
|
||||
, _total_window(&_total_count, window_size_seconds)
|
||||
, _backup_window(&_backup_count, window_size_seconds)
|
||||
, _cached_ratio(0.0)
|
||||
, _last_update_us(0) {
|
||||
}
|
||||
|
||||
// All atomic operations use relaxed ordering intentionally.
|
||||
// This is best-effort rate limiting: a slightly stale ratio is
|
||||
// acceptable for approximate throttling. Within a single update interval,
|
||||
// the cached ratio is not updated, so bursts up to update_interval_seconds
|
||||
// in duration can exceed the configured max_backup_ratio transiently.
|
||||
bool ShouldAllow() const {
|
||||
const int64_t now_us = butil::cpuwide_time_us();
|
||||
int64_t last_us = _last_update_us.load(butil::memory_order_relaxed);
|
||||
double ratio = _cached_ratio.load(butil::memory_order_relaxed);
|
||||
|
||||
if (now_us - last_us >= _update_interval_us) {
|
||||
if (_last_update_us.compare_exchange_strong(
|
||||
last_us, now_us, butil::memory_order_relaxed)) {
|
||||
int64_t total = _total_window.get_value();
|
||||
int64_t backup = _backup_window.get_value();
|
||||
// Fall back to cumulative counts when the window has no
|
||||
// sampled data yet (cold-start within the first few seconds).
|
||||
if (total <= 0) {
|
||||
total = _total_count.get_value();
|
||||
backup = _backup_count.get_value();
|
||||
}
|
||||
if (total > 0) {
|
||||
ratio = static_cast<double>(backup) / total;
|
||||
} else if (backup > 0) {
|
||||
// Backups issued but no completions in window yet (latency spike).
|
||||
// Be conservative to prevent backup storms.
|
||||
ratio = 1.0;
|
||||
} else {
|
||||
// True cold-start: no traffic yet. Allow freely.
|
||||
ratio = 0.0;
|
||||
}
|
||||
_cached_ratio.store(ratio, butil::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
bool allow = ratio < _max_backup_ratio;
|
||||
if (allow) {
|
||||
// Count backup decisions immediately for faster feedback
|
||||
// during latency spikes (before RPCs complete).
|
||||
_backup_count << 1;
|
||||
}
|
||||
return allow;
|
||||
}
|
||||
|
||||
void OnRPCEnd(const Controller* /*controller*/) {
|
||||
// Count each completed user-level RPC (called once per RPC, not per leg).
|
||||
// Backup decisions are counted in ShouldAllow() at decision time for
|
||||
// faster feedback. As a result, the effective suppression threshold is
|
||||
// (backup_count / total_count), where total_count is the number of
|
||||
// user RPCs that have completed.
|
||||
_total_count << 1;
|
||||
}
|
||||
|
||||
private:
|
||||
double _max_backup_ratio;
|
||||
int64_t _update_interval_us;
|
||||
|
||||
bvar::Adder<int64_t> _total_count;
|
||||
mutable bvar::Adder<int64_t> _backup_count;
|
||||
bvar::Window<bvar::Adder<int64_t>> _total_window;
|
||||
bvar::Window<bvar::Adder<int64_t>> _backup_window;
|
||||
|
||||
mutable butil::atomic<double> _cached_ratio;
|
||||
mutable butil::atomic<int64_t> _last_update_us;
|
||||
};
|
||||
|
||||
// Internal BackupRequestPolicy that composes a BackupRateLimiter
|
||||
// for ratio-based suppression.
|
||||
class RateLimitedBackupPolicy : public BackupRequestPolicy {
|
||||
public:
|
||||
RateLimitedBackupPolicy(int32_t backup_request_ms,
|
||||
double max_backup_ratio,
|
||||
int window_size_seconds,
|
||||
int update_interval_seconds)
|
||||
: _backup_request_ms(backup_request_ms)
|
||||
, _rate_limiter(max_backup_ratio, window_size_seconds,
|
||||
update_interval_seconds) {
|
||||
}
|
||||
|
||||
int32_t GetBackupRequestMs(const Controller* /*controller*/) const override {
|
||||
return _backup_request_ms;
|
||||
}
|
||||
|
||||
bool DoBackup(const Controller* /*controller*/) const override {
|
||||
return _rate_limiter.ShouldAllow();
|
||||
}
|
||||
|
||||
void OnRPCEnd(const Controller* controller) override {
|
||||
_rate_limiter.OnRPCEnd(controller);
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t _backup_request_ms;
|
||||
BackupRateLimiter _rate_limiter;
|
||||
};
|
||||
|
||||
BackupRequestPolicy* CreateRateLimitedBackupPolicy(
|
||||
const RateLimitedBackupPolicyOptions& options) {
|
||||
if (options.backup_request_ms < -1) {
|
||||
LOG(ERROR) << "Invalid backup_request_ms=" << options.backup_request_ms
|
||||
<< ", must be >= -1 (-1 means inherit from ChannelOptions)";
|
||||
return NULL;
|
||||
}
|
||||
if (options.max_backup_ratio <= 0 || options.max_backup_ratio > 1.0) {
|
||||
LOG(ERROR) << "Invalid max_backup_ratio=" << options.max_backup_ratio
|
||||
<< ", must be in (0, 1]";
|
||||
return NULL;
|
||||
}
|
||||
if (options.window_size_seconds < 1 || options.window_size_seconds > 3600) {
|
||||
LOG(ERROR) << "Invalid window_size_seconds=" << options.window_size_seconds
|
||||
<< ", must be in [1, 3600]";
|
||||
return NULL;
|
||||
}
|
||||
if (options.update_interval_seconds < 1) {
|
||||
LOG(ERROR) << "Invalid update_interval_seconds="
|
||||
<< options.update_interval_seconds << ", must be >= 1";
|
||||
return NULL;
|
||||
}
|
||||
if (options.update_interval_seconds > options.window_size_seconds) {
|
||||
LOG(WARNING) << "update_interval_seconds=" << options.update_interval_seconds
|
||||
<< " exceeds window_size_seconds=" << options.window_size_seconds
|
||||
<< "; the ratio window will rarely refresh within its own period";
|
||||
}
|
||||
// Plain new (without std::nothrow): brpc follows the project-wide convention
|
||||
// of letting OOM throw/abort rather than returning NULL. NULL return from
|
||||
// this factory already signals invalid parameters, not allocation failure.
|
||||
return new RateLimitedBackupPolicy(
|
||||
options.backup_request_ms, options.max_backup_ratio,
|
||||
options.window_size_seconds, options.update_interval_seconds);
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,80 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BACKUP_REQUEST_POLICY_H
|
||||
#define BRPC_BACKUP_REQUEST_POLICY_H
|
||||
|
||||
#include "brpc/controller.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class BackupRequestPolicy {
|
||||
public:
|
||||
virtual ~BackupRequestPolicy() = default;
|
||||
|
||||
// Return the time in milliseconds in which another request
|
||||
// will be sent if RPC does not finish.
|
||||
// Returning -1 means "inherit the backup_request_ms from ChannelOptions".
|
||||
// Returning any other negative value disables backup for this RPC.
|
||||
virtual int32_t GetBackupRequestMs(const Controller* controller) const = 0;
|
||||
|
||||
// Return true if the backup request should be sent.
|
||||
virtual bool DoBackup(const Controller* controller) const = 0;
|
||||
|
||||
// Called when an RPC ends; user can collect call information to adjust policy.
|
||||
virtual void OnRPCEnd(const Controller* controller) = 0;
|
||||
};
|
||||
|
||||
// Options for CreateRateLimitedBackupPolicy().
|
||||
// All fields have defaults matching the recommended starting values.
|
||||
struct RateLimitedBackupPolicyOptions {
|
||||
// Time in milliseconds after which a backup request is sent if the RPC
|
||||
// has not completed.
|
||||
// Use -1 (the default) to inherit the value from ChannelOptions.backup_request_ms.
|
||||
// Use >= 0 to override it explicitly.
|
||||
// Default: -1
|
||||
int32_t backup_request_ms = -1;
|
||||
|
||||
// Maximum ratio of backup requests to total requests in the sliding
|
||||
// window. Must be in (0, 1].
|
||||
// Default: 0.1
|
||||
double max_backup_ratio = 0.1;
|
||||
|
||||
// Width of the sliding time window in seconds. Must be in [1, 3600].
|
||||
// Default: 10
|
||||
int window_size_seconds = 10;
|
||||
|
||||
// Interval in seconds between cached-ratio refreshes. Must be >= 1.
|
||||
// Default: 5
|
||||
int update_interval_seconds = 5;
|
||||
};
|
||||
|
||||
// Create a BackupRequestPolicy that limits the ratio of backup requests
|
||||
// to total requests within a sliding time window. When the ratio reaches
|
||||
// or exceeds options.max_backup_ratio, DoBackup() returns false.
|
||||
// NOTE: Backup decisions are counted immediately at DoBackup() time for
|
||||
// fast feedback. Total RPCs are counted on completion (OnRPCEnd). During
|
||||
// latency spikes the ratio may temporarily lag until RPCs complete.
|
||||
// Returns NULL on invalid parameters.
|
||||
// The caller owns the returned pointer.
|
||||
BackupRequestPolicy* CreateRateLimitedBackupPolicy(
|
||||
const RateLimitedBackupPolicyOptions& options);
|
||||
|
||||
}
|
||||
|
||||
#endif // BRPC_BACKUP_REQUEST_POLICY_H
|
||||
@@ -0,0 +1,52 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include "brpc/baidu_master_service.h"
|
||||
#include "butil/logging.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
BaiduMasterService::BaiduMasterService()
|
||||
: _status(new (std::nothrow) MethodStatus), _ignore_eovercrowded(false) {
|
||||
LOG_IF(FATAL, NULL == _status) << "Fail to new MethodStatus";
|
||||
}
|
||||
|
||||
BaiduMasterService::~BaiduMasterService() {
|
||||
delete _status;
|
||||
_status = NULL;
|
||||
}
|
||||
|
||||
void BaiduMasterService::Describe(std::ostream &os,
|
||||
const DescribeOptions&) const {
|
||||
os << butil::class_name_str(*this);
|
||||
}
|
||||
|
||||
void BaiduMasterService::Expose(const butil::StringPiece& prefix) {
|
||||
if (NULL == _status) {
|
||||
return;
|
||||
}
|
||||
std::string s;
|
||||
const std::string& cached_name = butil::class_name_str(*this);
|
||||
s.reserve(prefix.size() + 1 + cached_name.size());
|
||||
s.append(prefix.data(), prefix.size());
|
||||
s.push_back('_');
|
||||
s.append(cached_name);
|
||||
_status->Expose(s);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BAIDU_MASTER_SERVICE_H
|
||||
#define BRPC_BAIDU_MASTER_SERVICE_H
|
||||
|
||||
#include <google/protobuf/service.h>
|
||||
#include <google/protobuf/stubs/callback.h>
|
||||
#include "brpc/details/method_status.h"
|
||||
#include "brpc/proto_base.pb.h"
|
||||
#include "brpc/controller.h"
|
||||
#include "brpc/serialized_request.h"
|
||||
#include "brpc/serialized_response.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
namespace policy {
|
||||
void ProcessRpcRequest(InputMessageBase* msg_base);
|
||||
}
|
||||
|
||||
class BaiduMasterService : public ::google::protobuf::Service
|
||||
, public Describable {
|
||||
public:
|
||||
BaiduMasterService();
|
||||
~BaiduMasterService() override;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BaiduMasterService);
|
||||
|
||||
AdaptiveMaxConcurrency& MaxConcurrency() {
|
||||
return _max_concurrency;
|
||||
}
|
||||
|
||||
bool ignore_eovercrowded() {
|
||||
return _ignore_eovercrowded;
|
||||
}
|
||||
|
||||
void set_ignore_eovercrowded(bool ignore_eovercrowded) {
|
||||
_ignore_eovercrowded = ignore_eovercrowded;
|
||||
}
|
||||
|
||||
virtual void ProcessRpcRequest(Controller* cntl,
|
||||
const SerializedRequest* request,
|
||||
SerializedResponse* response,
|
||||
::google::protobuf::Closure* done) = 0;
|
||||
|
||||
|
||||
// Put descriptions into the stream.
|
||||
void Describe(std::ostream &os, const DescribeOptions&) const override;
|
||||
|
||||
// implements Service ----------------------------------------------
|
||||
|
||||
void CallMethod(const ::google::protobuf::MethodDescriptor*,
|
||||
::google::protobuf::RpcController* controller,
|
||||
const ::google::protobuf::Message* request,
|
||||
::google::protobuf::Message* response,
|
||||
::google::protobuf::Closure* done) override {
|
||||
ProcessRpcRequest((Controller*)controller,
|
||||
(const SerializedRequest*)request,
|
||||
(SerializedResponse*)response, done);
|
||||
}
|
||||
|
||||
const ::google::protobuf::ServiceDescriptor* GetDescriptor() override {
|
||||
return BaiduMasterServiceBase::descriptor();
|
||||
}
|
||||
|
||||
const ::google::protobuf::Message& GetRequestPrototype(
|
||||
const ::google::protobuf::MethodDescriptor*) const override {
|
||||
return _default_request;
|
||||
}
|
||||
|
||||
const ::google::protobuf::Message& GetResponsePrototype(
|
||||
const ::google::protobuf::MethodDescriptor*) const override {
|
||||
return _default_response;
|
||||
}
|
||||
|
||||
private:
|
||||
friend void policy::ProcessRpcRequest(InputMessageBase* msg_base);
|
||||
friend class StatusService;
|
||||
friend class Server;
|
||||
|
||||
void Expose(const butil::StringPiece& prefix);
|
||||
|
||||
SerializedRequest _default_request;
|
||||
SerializedResponse _default_response;
|
||||
|
||||
MethodStatus* _status;
|
||||
AdaptiveMaxConcurrency _max_concurrency;
|
||||
bool _ignore_eovercrowded;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BRPC_BAIDU_MASTER_SERVICE_H
|
||||
@@ -0,0 +1,67 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
#include <google/protobuf/descriptor.h>
|
||||
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/server.h"
|
||||
#include "brpc/errno.pb.h"
|
||||
#include "brpc/builtin/bad_method_service.h"
|
||||
#include "brpc/details/server_private_accessor.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
void BadMethodService::no_method(::google::protobuf::RpcController* cntl_base,
|
||||
const BadMethodRequest* request,
|
||||
BadMethodResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
const Server* server = cntl->server();
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
const char* newline = (use_html ? "<br>\n" : "\n");
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
|
||||
std::ostringstream os;
|
||||
os << "Missing method name for service=" << request->service_name() << '.';
|
||||
const Server::ServiceProperty* sp = ServerPrivateAccessor(server)
|
||||
.FindServicePropertyAdaptively(request->service_name());
|
||||
if (sp != NULL && sp->service != NULL) {
|
||||
const google::protobuf::ServiceDescriptor* sd =
|
||||
sp->service->GetDescriptor();
|
||||
os << " Available methods are: " << newline << newline;
|
||||
for (int i = 0; i < sd->method_count(); ++i) {
|
||||
os << "rpc " << sd->method(i)->name()
|
||||
<< " (" << sd->method(i)->input_type()->name()
|
||||
<< ") returns (" << sd->method(i)->output_type()->name()
|
||||
<< ");" << newline;
|
||||
}
|
||||
}
|
||||
if (sp != NULL && sp->restful_map != NULL) {
|
||||
os << " This path is associated with a RestfulMap!";
|
||||
}
|
||||
cntl->SetFailed(ENOMETHOD, "%s", os.str().c_str());
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,39 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BADMETHOD_SERVICE_H
|
||||
#define BRPC_BADMETHOD_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class BadMethodService : public badmethod {
|
||||
public:
|
||||
void no_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::BadMethodRequest* request,
|
||||
::brpc::BadMethodResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
|
||||
#endif // BRPC_BADMETHOD_SERVICE_H
|
||||
@@ -0,0 +1,75 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include "bthread/bthread.h"
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/builtin/bthreads_service.h"
|
||||
|
||||
namespace bthread {
|
||||
extern void print_task(std::ostream& os, bthread_t tid, bool enable_trace,
|
||||
bool ignore_not_matched = false);
|
||||
extern void print_living_tasks(std::ostream& os, bool enable_trace);
|
||||
}
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
void BthreadsService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::BthreadsRequest*,
|
||||
::brpc::BthreadsResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
butil::IOBufBuilder os;
|
||||
const std::string& constraint = cntl->http_request().unresolved_path();
|
||||
if (constraint.empty()) {
|
||||
#ifdef BRPC_BTHREAD_TRACER
|
||||
os << "Use /bthreads/<bthread_id> or /bthreads/<bthread_id>?st=1 for stack trace\n";
|
||||
os << "To check all living bthread, use /bthreads/all or /bthreads/all?st=1 for stack trace\n";
|
||||
#else
|
||||
os << "Use /bthreads/<bthread_id>\n";
|
||||
os << "To check all living bthread, use /bthreads/all\n";
|
||||
#endif // BRPC_BTHREAD_TRACER
|
||||
} else {
|
||||
bool enable_trace = false;
|
||||
#ifdef BRPC_BTHREAD_TRACER
|
||||
const std::string* st = cntl->http_request().uri().GetQuery("st");
|
||||
if (NULL != st && *st == "1") {
|
||||
enable_trace = true;
|
||||
}
|
||||
#endif // BRPC_BTHREAD_TRACER
|
||||
char* endptr = NULL;
|
||||
bthread_t tid = strtoull(constraint.c_str(), &endptr, 10);
|
||||
if (*endptr == '\0' || *endptr == '/' || *endptr == '?') {
|
||||
::bthread::print_task(os, tid, enable_trace);
|
||||
}
|
||||
else if (constraint != "all" && constraint != "all?st=1") {
|
||||
cntl->SetFailed(ENOMETHOD, "path=%s is not a bthread id or all, or all?st=1\n",
|
||||
constraint.c_str());
|
||||
} else {
|
||||
::bthread::print_living_tasks(os, enable_trace);
|
||||
}
|
||||
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,38 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BTHREADS_SERVICE_H
|
||||
#define BRPC_BTHREADS_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class BthreadsService : public bthreads {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::BthreadsRequest* request,
|
||||
::brpc::BthreadsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_BTHREADS_SERVICE_H
|
||||
@@ -0,0 +1,399 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <iomanip>
|
||||
#include <sys/time.h>
|
||||
#include <fcntl.h> // O_RDONLY
|
||||
#include <gflags/gflags.h>
|
||||
#include "butil/logging.h"
|
||||
#include "butil/fd_guard.h" // fd_guard
|
||||
#include "butil/file_util.h" // butil::FilePath
|
||||
#include "butil/third_party/murmurhash3/murmurhash3.h"
|
||||
#include "butil/process_util.h" // ReadCommandLine
|
||||
#include "brpc/server.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
DEFINE_string(rpc_profiling_dir, "./rpc_data/profiling",
|
||||
"For storing profiling results.");
|
||||
|
||||
bool UseHTML(const HttpHeader& header) {
|
||||
const std::string* console = header.uri().GetQuery(CONSOLE_STR);
|
||||
if (console != NULL) {
|
||||
return atoi(console->c_str()) == 0;
|
||||
}
|
||||
// [curl header]
|
||||
// User-Agent: curl/7.12.1 (x86_64-redhat-linux-gnu) libcurl/7.12.1 ...
|
||||
const std::string* agent = header.GetHeader(USER_AGENT_STR);
|
||||
if (agent == NULL) { // use text when user-agent is absent
|
||||
return false;
|
||||
}
|
||||
return agent->find("curl/") == std::string::npos;
|
||||
}
|
||||
|
||||
// Written by Jack Handy
|
||||
// <A href="mailto:jakkhandy@hotmail.com">jakkhandy@hotmail.com</A>
|
||||
inline bool url_wildcmp(const char* wild, const char* str) {
|
||||
const char* cp = NULL;
|
||||
const char* mp = NULL;
|
||||
|
||||
while (*str && *wild != '*') {
|
||||
if (*wild != *str && *wild != '$') {
|
||||
return false;
|
||||
}
|
||||
++wild;
|
||||
++str;
|
||||
}
|
||||
|
||||
while (*str) {
|
||||
if (*wild == '*') {
|
||||
if (!*++wild) {
|
||||
return true;
|
||||
}
|
||||
mp = wild;
|
||||
cp = str+1;
|
||||
} else if (*wild == *str || *wild == '$') {
|
||||
++wild;
|
||||
++str;
|
||||
} else {
|
||||
wild = mp;
|
||||
str = cp++;
|
||||
}
|
||||
}
|
||||
|
||||
while (*wild == '*') {
|
||||
++wild;
|
||||
}
|
||||
return !*wild;
|
||||
}
|
||||
|
||||
bool MatchAnyWildcard(const std::string& name,
|
||||
const std::vector<std::string>& wildcards) {
|
||||
for (size_t i = 0; i < wildcards.size(); ++i) {
|
||||
if (url_wildcmp(wildcards[i].c_str(), name.c_str())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PrintRealDateTime(std::ostream& os, int64_t tm) {
|
||||
char buf[32];
|
||||
const time_t tm_s = tm / 1000000L;
|
||||
struct tm lt;
|
||||
strftime(buf, sizeof(buf), "%Y/%m/%d-%H:%M:%S.", localtime_r(&tm_s, <));
|
||||
const char old_fill = os.fill('0');
|
||||
os << buf << std::setw(6) << tm % 1000000L;
|
||||
os.fill(old_fill);
|
||||
}
|
||||
|
||||
void PrintRealDateTime(std::ostream& os, int64_t tm,
|
||||
bool ignore_microseconds) {
|
||||
char buf[32];
|
||||
const time_t tm_s = tm / 1000000L;
|
||||
struct tm lt;
|
||||
strftime(buf, sizeof(buf), "%Y/%m/%d-%H:%M:%S", localtime_r(&tm_s, <));
|
||||
if (ignore_microseconds) {
|
||||
os << buf;
|
||||
} else {
|
||||
const char old_fill = os.fill('0');
|
||||
os << buf << '.' << std::setw(6) << tm % 1000000L;
|
||||
os.fill(old_fill);
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const PrintedAsDateTime& d) {
|
||||
PrintRealDateTime(os, d.realtime);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Path& link) {
|
||||
if (link.html_addr) {
|
||||
if (link.html_addr != Path::LOCAL) {
|
||||
os << "<a href=\"http://" << *link.html_addr << link.uri << "\">";
|
||||
} else {
|
||||
os << "<a href=\"" << link.uri << "\">";
|
||||
}
|
||||
}
|
||||
if (link.text) {
|
||||
os << link.text;
|
||||
} else {
|
||||
os << link.uri;
|
||||
}
|
||||
if (link.html_addr) {
|
||||
os << "</a>";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
const butil::EndPoint *Path::LOCAL = (butil::EndPoint *)0x01;
|
||||
|
||||
void AppendFileName(std::string* dir, const std::string& filename) {
|
||||
if (dir->empty()) {
|
||||
dir->append(filename);
|
||||
return;
|
||||
}
|
||||
const size_t len = filename.size();
|
||||
if (len >= 3) {
|
||||
if (butil::back_char(*dir) != '/') {
|
||||
dir->push_back('/');
|
||||
}
|
||||
dir->append(filename);
|
||||
} else if (len == 1) {
|
||||
if (filename[0] != '.') {
|
||||
if (butil::back_char(*dir) != '/') {
|
||||
dir->push_back('/');
|
||||
}
|
||||
dir->append(filename);
|
||||
}
|
||||
} else if (len == 2) {
|
||||
if (filename[0] != '.' || filename[1] != '.') {
|
||||
if (butil::back_char(*dir) != '/') {
|
||||
dir->push_back('/');
|
||||
}
|
||||
dir->append(filename);
|
||||
} else {
|
||||
const bool is_abs = (dir->c_str()[0] == '/');
|
||||
int npop = 1;
|
||||
while (npop > 0) {
|
||||
const char* p = dir->c_str() + dir->size() - 1;
|
||||
for (; p != dir->c_str() && *p == '/'; --p);
|
||||
if (p == dir->c_str()) {
|
||||
dir->clear();
|
||||
break;
|
||||
}
|
||||
dir->resize(p - dir->c_str() + 1);
|
||||
|
||||
size_t slash_pos = dir->find_last_of('/');
|
||||
if (slash_pos == std::string::npos) {
|
||||
--npop;
|
||||
dir->clear();
|
||||
break;
|
||||
}
|
||||
if (strcmp(dir->data() + slash_pos + 1, ".") != 0) {
|
||||
if (strcmp(dir->data() + slash_pos + 1, "..") == 0) {
|
||||
++npop;
|
||||
} else {
|
||||
--npop;
|
||||
}
|
||||
}
|
||||
ssize_t new_pos = (ssize_t)slash_pos - 1;
|
||||
for (; new_pos >= 0 && (*dir)[new_pos] == '/'; --new_pos);
|
||||
dir->resize(new_pos + 1);
|
||||
if (dir->empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dir->empty()) {
|
||||
if (is_abs) {
|
||||
dir->push_back('/');
|
||||
} else {
|
||||
if (npop > 0) {
|
||||
dir->append("..");
|
||||
for (int i = 1; i < npop; ++i) {
|
||||
dir->append("/..");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // else len == 0, nothing to do
|
||||
}
|
||||
|
||||
const char* gridtable_style() {
|
||||
return
|
||||
"<style type=\"text/css\">\n"
|
||||
"table.gridtable {\n"
|
||||
" color:#333333;\n"
|
||||
" border-width:1px;\n"
|
||||
" border-color:#666666;\n"
|
||||
" border-collapse:collapse;\n"
|
||||
"}\n"
|
||||
"table.gridtable th {\n"
|
||||
" border-width:1px;\n"
|
||||
" padding:3px;\n"
|
||||
" border-style:solid;\n"
|
||||
" border-color:#666666;\n"
|
||||
" background-color:#eeeeee;\n"
|
||||
"}\n"
|
||||
"table.gridtable td {\n"
|
||||
" border-width:1px;\n"
|
||||
" padding:3px;\n"
|
||||
" border-style:solid;\n"
|
||||
" border-color:#666666;\n"
|
||||
" background-color:#ffffff;\n"
|
||||
"}\n"
|
||||
"</style>\n";
|
||||
}
|
||||
|
||||
const char* TabsHead() {
|
||||
return
|
||||
"<style type=\"text/css\">\n"
|
||||
"ol,ul { list-style:none; }\n"
|
||||
".tabs-menu {\n"
|
||||
" position: fixed;"
|
||||
" top: 0px;"
|
||||
" left: 0px;"
|
||||
" height: 40px;\n"
|
||||
" width: 100%;\n"
|
||||
" clear: both;\n"
|
||||
" padding: 0px;\n"
|
||||
" margin: 0px;\n"
|
||||
" background-color: #606060;\n"
|
||||
" border:none;\n"
|
||||
" overflow: hidden;\n"
|
||||
" box-shadow: 0px 1px 2px #909090;\n"
|
||||
" z-index: 5;\n"
|
||||
"}\n"
|
||||
".tabs-menu li {\n"
|
||||
" float:left;\n"
|
||||
" fill:none;\n"
|
||||
" border:none;\n"
|
||||
" padding:10px 30px 10px 30px;\n"
|
||||
" text-align:center;\n"
|
||||
" cursor:pointer;\n"
|
||||
" color:#dddddd;\n"
|
||||
" font-weight: bold;\n"
|
||||
" font-family: \"Segoe UI\", Calibri, Arial;\n"
|
||||
"}\n"
|
||||
".tabs-menu li.current {\n"
|
||||
" color:#FFFFFF;\n"
|
||||
" background-color: #303030;\n"
|
||||
"}\n"
|
||||
".tabs-menu li.help {\n"
|
||||
" float:right;\n"
|
||||
"}\n"
|
||||
".tabs-menu li:hover {\n"
|
||||
" background-color: #303030;\n"
|
||||
"}\n"
|
||||
"</style>\n"
|
||||
"<script type=\"text/javascript\">\n"
|
||||
"$(function() {\n"
|
||||
" $(\".tabs-menu li\").click(function(event) {\n"
|
||||
" window.location.href = $(this).attr('id');\n"
|
||||
" });\n"
|
||||
"});\n"
|
||||
"</script>\n";
|
||||
}
|
||||
|
||||
const char* logo() {
|
||||
return
|
||||
" __\n"
|
||||
" / /_ _________ _____\n"
|
||||
" / __ \\/ ___/ __ \\/ ___/\n"
|
||||
" / /_/ / / / /_/ / /__\n"
|
||||
"/_.___/_/ / .___/\\___/\n"
|
||||
" /_/\n";
|
||||
}
|
||||
|
||||
const char* ProfilingType2String(ProfilingType t) {
|
||||
switch (t) {
|
||||
case PROFILING_CPU: return "cpu";
|
||||
case PROFILING_HEAP: return "heap";
|
||||
case PROFILING_GROWTH: return "growth";
|
||||
case PROFILING_CONTENTION: return "contention";
|
||||
case PROFILING_IOBUF: return "iobuf";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
int FileChecksum(const char* file_path, unsigned char* checksum) {
|
||||
butil::fd_guard fd(open(file_path, O_RDONLY));
|
||||
if (fd < 0) {
|
||||
PLOG(ERROR) << "Fail to open `" << file_path << "'";
|
||||
return -1;
|
||||
}
|
||||
char block[16*1024]; // 16k each time
|
||||
ssize_t size = 0L;
|
||||
butil::MurmurHash3_x64_128_Context mm_ctx;
|
||||
butil::MurmurHash3_x64_128_Init(&mm_ctx, 0);
|
||||
while ((size = read(fd, block, sizeof(block))) > 0) {
|
||||
butil::MurmurHash3_x64_128_Update(&mm_ctx, block, size);
|
||||
}
|
||||
butil::MurmurHash3_x64_128_Final(checksum, &mm_ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pthread_once_t create_program_name_once = PTHREAD_ONCE_INIT;
|
||||
static const char* s_program_name = "unknown";
|
||||
static char s_cmdline[256];
|
||||
static void CreateProgramName() {
|
||||
const ssize_t nr = butil::ReadCommandLine(s_cmdline, sizeof(s_cmdline) - 1, false);
|
||||
if (nr > 0) {
|
||||
s_cmdline[nr] = '\0';
|
||||
s_program_name = s_cmdline;
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetProgramName() {
|
||||
pthread_once(&create_program_name_once, CreateProgramName);
|
||||
return s_program_name;
|
||||
}
|
||||
|
||||
static pthread_once_t create_program_path_once = PTHREAD_ONCE_INIT;
|
||||
static const char* s_program_path = "unknown";
|
||||
static char s_program_exec_path[PATH_MAX];
|
||||
static void CreateProgramPath() {
|
||||
const ssize_t nr = butil::GetProcessAbsolutePath(s_program_exec_path, sizeof(s_program_exec_path) - 1);
|
||||
if (nr > 0) {
|
||||
s_program_exec_path[nr] = '\0';
|
||||
s_program_path = s_program_exec_path;
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetProgramPath() {
|
||||
pthread_once(&create_program_path_once, CreateProgramPath);
|
||||
return s_program_path;
|
||||
}
|
||||
|
||||
|
||||
static pthread_once_t compute_program_checksum_once = PTHREAD_ONCE_INIT;
|
||||
static char s_program_checksum[33];
|
||||
static const char s_alphabet[] = "0123456789abcdef";
|
||||
static void ComputeProgramCHECKSUM() {
|
||||
unsigned char checksum[16];
|
||||
FileChecksum(GetProgramPath(), checksum);
|
||||
for (size_t i = 0, j = 0; i < 16; ++i, j+=2) {
|
||||
s_program_checksum[j] = s_alphabet[checksum[i] >> 4];
|
||||
s_program_checksum[j+1] = s_alphabet[checksum[i] & 0xF];
|
||||
}
|
||||
s_program_checksum[32] = '\0';
|
||||
|
||||
}
|
||||
const char* GetProgramChecksum() {
|
||||
pthread_once(&compute_program_checksum_once, ComputeProgramCHECKSUM);
|
||||
return s_program_checksum;
|
||||
}
|
||||
|
||||
bool SupportGzip(Controller* cntl) {
|
||||
const std::string* encodings =
|
||||
cntl->http_request().GetHeader("Accept-Encoding");
|
||||
if (encodings == NULL) {
|
||||
return false;
|
||||
}
|
||||
return encodings->find("gzip") != std::string::npos;
|
||||
}
|
||||
|
||||
void Time2GMT(time_t t, char* buf, size_t size) {
|
||||
struct tm tm;
|
||||
gmtime_r(&t, &tm);
|
||||
strftime(buf, size, "%a, %d %b %Y %H:%M:%S %Z", &tm);
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,150 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BUILTIN_COMMON_H
|
||||
#define BRPC_BUILTIN_COMMON_H
|
||||
|
||||
#include <vector> // std::vector
|
||||
#include <gflags/gflags_declare.h>
|
||||
#include "butil/endpoint.h"
|
||||
#include "brpc/http_header.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class Controller;
|
||||
|
||||
// These static strings are referenced more than once in brpc.
|
||||
// Don't turn them to std::strings whose constructing sequences are undefined.
|
||||
const char* const UNKNOWN_METHOD_STR = "unknown_method";
|
||||
const char* const TRACE_ID_STR = "trace";
|
||||
const char* const SPAN_ID_STR = "span";
|
||||
const char* const TIME_STR = "time";
|
||||
const char* const MAX_SCAN_STR = "max_scan";
|
||||
const char* const MIN_LATENCY_STR = "min_latency";
|
||||
const char* const MIN_REQUEST_SIZE_STR = "min_request_size";
|
||||
const char* const MIN_RESPONSE_SIZE_STR = "min_response_size";
|
||||
const char* const LOG_ID_STR = "log_id";
|
||||
const char* const ERROR_CODE_STR = "error_code";
|
||||
const char* const CONSOLE_STR = "console";
|
||||
const char* const USER_AGENT_STR = "user-agent";
|
||||
const char* const SETVALUE_STR = "setvalue";
|
||||
|
||||
const size_t MAX_READ = 1024 * 1024;
|
||||
|
||||
enum ProfilingType {
|
||||
PROFILING_CPU = 0,
|
||||
PROFILING_HEAP = 1,
|
||||
PROFILING_GROWTH = 2,
|
||||
PROFILING_CONTENTION = 3,
|
||||
PROFILING_IOBUF = 4,
|
||||
};
|
||||
|
||||
DECLARE_string(rpc_profiling_dir);
|
||||
|
||||
bool UseHTML(const HttpHeader& header);
|
||||
bool MatchAnyWildcard(const std::string& name,
|
||||
const std::vector<std::string>& wildcards);
|
||||
|
||||
void PrintRealDateTime(std::ostream& os, int64_t tm);
|
||||
void PrintRealDateTime(std::ostream& os, int64_t tm, bool ignore_microseconds);
|
||||
|
||||
struct PrintedAsDateTime {
|
||||
PrintedAsDateTime(int64_t realtime2) : realtime(realtime2) {}
|
||||
int64_t realtime;
|
||||
};
|
||||
std::ostream& operator<<(std::ostream& os, const PrintedAsDateTime&);
|
||||
|
||||
struct Path {
|
||||
static const butil::EndPoint *LOCAL;
|
||||
Path(const char* uri2, const butil::EndPoint* html_addr2)
|
||||
: uri(uri2), html_addr(html_addr2), text(NULL) {}
|
||||
|
||||
Path(const char* uri2, const butil::EndPoint* html_addr2, const char* text2)
|
||||
: uri(uri2), html_addr(html_addr2), text(text2) {}
|
||||
|
||||
const char* uri;
|
||||
const butil::EndPoint* html_addr;
|
||||
const char* text;
|
||||
};
|
||||
std::ostream& operator<<(std::ostream& os, const Path& link);
|
||||
|
||||
// Append `filename' to `dir' according to unix directory rules:
|
||||
// "foo/bar" + ".." -> "foo"
|
||||
// "foo/bar/." + ".." -> "foo"
|
||||
// "foo" + "." -> "foo"
|
||||
// "foo/" + ".." -> ""
|
||||
// "foo/../" + ".." -> ".."
|
||||
// "/foo/../" + ".." -> "/"
|
||||
// "foo/./" + ".." -> ""
|
||||
void AppendFileName(std::string* dir, const std::string& filename);
|
||||
|
||||
// style of class=gridtable, wrapped with <style>
|
||||
const char* gridtable_style();
|
||||
|
||||
// Put inside <head></head> of html to work with Tabbed.
|
||||
const char* TabsHead();
|
||||
|
||||
// The logo ascii art.
|
||||
const char* logo();
|
||||
|
||||
// Convert ProfilingType to its description.
|
||||
const char* ProfilingType2String(ProfilingType t);
|
||||
|
||||
// Compute 128-bit checksum of the file at `file_path'.
|
||||
// Return 0 on success.
|
||||
int FileChecksum(const char* file_path, unsigned char* checksum);
|
||||
|
||||
// Get name of current program.
|
||||
const char* GetProgramName();
|
||||
|
||||
// Get absolute path of current program.
|
||||
const char* GetProgramPath();
|
||||
|
||||
// Get checksum of current program image.
|
||||
const char* GetProgramChecksum();
|
||||
|
||||
// True if the http requester support gzip compression.
|
||||
bool SupportGzip(Controller* cntl);
|
||||
|
||||
void Time2GMT(time_t t, char* buf, size_t size);
|
||||
|
||||
template <typename T>
|
||||
struct MinWidth {
|
||||
MinWidth(const T& obj2, size_t nspace2) : obj(&obj2), nspace(nspace2) {}
|
||||
const T* obj;
|
||||
size_t nspace;
|
||||
};
|
||||
template <typename T>
|
||||
MinWidth<T> min_width(const T& obj, size_t nspace) {
|
||||
return MinWidth<T>(obj, nspace);
|
||||
}
|
||||
template <typename T>
|
||||
inline std::ostream& operator<<(std::ostream& os, const MinWidth<T>& fw) {
|
||||
const std::streampos old_pos = os.tellp();
|
||||
os << *fw.obj;
|
||||
for (size_t i = os.tellp() - old_pos; i < fw.nspace; ++i) {
|
||||
os << ' ';
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_BUILTIN_COMMON_H
|
||||
@@ -0,0 +1,411 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <ostream>
|
||||
#include <iomanip>
|
||||
#include <netinet/tcp.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/socket_map.h" // SocketMapList
|
||||
#include "brpc/acceptor.h" // Acceptor
|
||||
#include "brpc/server.h"
|
||||
#include "brpc/nshead_service.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/builtin/connections_service.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
int64_t GetChannelConnectionCount();
|
||||
|
||||
DEFINE_bool(show_hostname_instead_of_ip, false,
|
||||
"/connections shows hostname instead of ip");
|
||||
BRPC_VALIDATE_GFLAG(show_hostname_instead_of_ip, PassValidate);
|
||||
|
||||
DEFINE_int32(max_shown_connections, 1024,
|
||||
"Print stats of at most so many connections (soft limit)");
|
||||
|
||||
// NOTE: The returned string must be 3-char wide in text mode.
|
||||
inline const char* SSLStateToYesNo(SSLState s, bool use_html) {
|
||||
switch (s) {
|
||||
case SSL_UNKNOWN:
|
||||
return (use_html ? "-" : " - ");
|
||||
case SSL_CONNECTING:
|
||||
case SSL_CONNECTED:
|
||||
return "Yes";
|
||||
case SSL_OFF:
|
||||
return "No ";
|
||||
}
|
||||
return "Bad";
|
||||
}
|
||||
|
||||
struct NameOfPoint {
|
||||
explicit NameOfPoint(const butil::EndPoint& pt_) : pt(pt_) {}
|
||||
butil::EndPoint pt;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const NameOfPoint& nop) {
|
||||
char buf[128];
|
||||
if (FLAGS_show_hostname_instead_of_ip &&
|
||||
butil::endpoint2hostname(nop.pt, buf, sizeof(buf)) == 0) {
|
||||
return os << buf;
|
||||
} else {
|
||||
return os << nop.pt;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool EndsWith(const std::string& str, const char* suffix) {
|
||||
const size_t len = strlen(suffix);
|
||||
return str.size() >= len &&
|
||||
memcmp(str.data() + str.size() - len, suffix, len) == 0;
|
||||
}
|
||||
|
||||
static std::string BriefName(const std::string& cname) {
|
||||
size_t pos = cname.rfind(':');
|
||||
if (pos == std::string::npos) {
|
||||
pos = 0;
|
||||
} else {
|
||||
++pos;
|
||||
}
|
||||
size_t end = cname.size();
|
||||
if (EndsWith(cname, "ServiceAdaptor")) {
|
||||
end = cname.size() - 14;
|
||||
} else if (EndsWith(cname, "Adaptor")) {
|
||||
end = cname.size() - 7;
|
||||
} else if (EndsWith(cname, "Protocol")) {
|
||||
end = cname.size() - 8;
|
||||
} else if (EndsWith(cname, "Service")) {
|
||||
end = cname.size() - 7;
|
||||
}
|
||||
std::string tmp;
|
||||
tmp.reserve(end - pos + 4);
|
||||
for (; pos < end; ++pos) {
|
||||
if (::isupper(cname[pos])) {
|
||||
if (!tmp.empty()) {
|
||||
tmp.push_back('_');
|
||||
}
|
||||
tmp.push_back(::tolower(cname[pos]));
|
||||
} else {
|
||||
tmp.push_back(cname[pos]);
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void ConnectionsService::PrintConnections(
|
||||
std::ostream& os, const std::vector<SocketId>& conns,
|
||||
bool use_html, const Server* server, bool is_channel_conn) const {
|
||||
if (conns.empty()) {
|
||||
return;
|
||||
}
|
||||
if (use_html) {
|
||||
os << "<table class=\"gridtable sortable\" border=\"1\"><tr>"
|
||||
"<th>CreatedTime</th>"
|
||||
"<th>RemoteSide</th>";
|
||||
if (is_channel_conn) {
|
||||
os << "<th>Local</th>"
|
||||
"<th>RecentErr</th>"
|
||||
"<th>nbreak</th>";
|
||||
}
|
||||
os << "<th>SSL</th>"
|
||||
"<th>Protocol</th>"
|
||||
"<th>fd</th>"
|
||||
"<th>InBytes/s</th>"
|
||||
"<th>In/s</th>"
|
||||
"<th>InBytes/m</th>"
|
||||
"<th>In/m</th>"
|
||||
"<th>OutBytes/s</th>"
|
||||
"<th>Out/s</th>"
|
||||
"<th>OutBytes/m</th>"
|
||||
"<th>Out/m</th>"
|
||||
"<th>Rtt/Var(ms)</th>"
|
||||
"<th>SocketId</th>"
|
||||
"</tr>\n";
|
||||
} else {
|
||||
os << "CreatedTime |RemoteSide |";
|
||||
if (is_channel_conn) {
|
||||
os << "Local|RecentErr|nbreak|";
|
||||
}
|
||||
os << "SSL|Protocol |fd |"
|
||||
"InBytes/s|In/s |InBytes/m |In/m |"
|
||||
"OutBytes/s|Out/s |OutBytes/m|Out/m |"
|
||||
"Rtt/Var(ms)|SocketId\n";
|
||||
}
|
||||
|
||||
const char* const bar = (use_html ? "</td><td>" : "|");
|
||||
SocketStat stat;
|
||||
std::string nshead_service_name; // shared in following for iterations.
|
||||
std::vector<SocketId> first_id;
|
||||
for (size_t i = 0; i < conns.size(); ++i) {
|
||||
const SocketId socket_id = conns[i];
|
||||
SocketUniquePtr ptr;
|
||||
bool failed = false;
|
||||
if (Socket::Address(socket_id, &ptr) != 0) {
|
||||
int ret = Socket::AddressFailedAsWell(socket_id, &ptr);
|
||||
if (ret < 0) {
|
||||
continue;
|
||||
} else if (ret > 0) {
|
||||
if (!ptr->HCEnabled()) {
|
||||
// Sockets without HC will soon be destroyed
|
||||
continue;
|
||||
}
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_html) {
|
||||
os << "<tr><td>";
|
||||
}
|
||||
if (failed) {
|
||||
os << min_width("Broken", 26) << bar
|
||||
<< min_width(NameOfPoint(ptr->remote_side()), 19) << bar;
|
||||
if (is_channel_conn) {
|
||||
os << min_width(ptr->local_side().port, 5) << bar
|
||||
<< min_width(ptr->recent_error_count(), 10) << bar
|
||||
<< min_width(ptr->isolated_times(), 7) << bar;
|
||||
}
|
||||
os << min_width("-", 3) << bar
|
||||
<< min_width("-", 12) << bar
|
||||
<< min_width("-", 5) << bar
|
||||
<< min_width("-", 9) << bar
|
||||
<< min_width("-", 6) << bar
|
||||
<< min_width("-", 10) << bar
|
||||
<< min_width("-", 8) << bar
|
||||
<< min_width("-", 10) << bar
|
||||
<< min_width("-", 6) << bar
|
||||
<< min_width("-", 10) << bar
|
||||
<< min_width("-", 8) << bar
|
||||
<< min_width("-", 11) << bar;
|
||||
} else {
|
||||
{
|
||||
SocketUniquePtr agent_sock;
|
||||
if (ptr->PeekAgentSocket(&agent_sock) == 0) {
|
||||
ptr.swap(agent_sock);
|
||||
}
|
||||
}
|
||||
// Get name of the protocol. In principle we can dynamic_cast the
|
||||
// socket user to InputMessenger but I'm not sure if that's a bit
|
||||
// slow (because we have many connections here).
|
||||
int pref_index = ptr->preferred_index();
|
||||
SocketUniquePtr first_sub;
|
||||
int pooled_count = -1;
|
||||
if (ptr->HasSocketPool()) {
|
||||
int numfree = 0;
|
||||
int numinflight = 0;
|
||||
if (ptr->GetPooledSocketStats(&numfree, &numinflight)) {
|
||||
pooled_count = numfree + numinflight;
|
||||
}
|
||||
// Check preferred_index of any pooled sockets.
|
||||
ptr->ListPooledSockets(&first_id, 1);
|
||||
if (!first_id.empty()) {
|
||||
Socket::Address(first_id[0], &first_sub);
|
||||
}
|
||||
}
|
||||
const char* pref_prot = "-";
|
||||
if (ptr->user() == server->_am) {
|
||||
pref_prot = server->_am->NameOfProtocol(pref_index);
|
||||
// Special treatment for nshead services. Notice that
|
||||
// pref_index is comparable to ProtocolType after r31951
|
||||
if (pref_index == (int)PROTOCOL_NSHEAD &&
|
||||
server->options().nshead_service != NULL) {
|
||||
if (nshead_service_name.empty()) {
|
||||
nshead_service_name = BriefName(butil::class_name_str(
|
||||
*server->options().nshead_service));
|
||||
}
|
||||
pref_prot = nshead_service_name.c_str();
|
||||
}
|
||||
} else if (ptr->CreatedByConnect()) {
|
||||
pref_prot = get_client_side_messenger()->NameOfProtocol(pref_index);
|
||||
}
|
||||
if (strcmp(pref_prot, "unknown") == 0) {
|
||||
// Show unknown protocol as - to be consistent with other columns.
|
||||
pref_prot = "-";
|
||||
} else if (strcmp(pref_prot, "h2") == 0) {
|
||||
if (!ptr->is_ssl()) {
|
||||
pref_prot = "h2c";
|
||||
}
|
||||
}
|
||||
ptr->GetStat(&stat);
|
||||
PrintRealDateTime(os, ptr->_reset_fd_real_us);
|
||||
int rttfd = ptr->fd();
|
||||
if (rttfd < 0 && first_sub != NULL) {
|
||||
rttfd = first_sub->fd();
|
||||
}
|
||||
|
||||
bool got_rtt = false;
|
||||
uint32_t srtt = 0;
|
||||
uint32_t rtt_var = 0;
|
||||
// get rtt
|
||||
#if defined(OS_LINUX)
|
||||
struct tcp_info ti;
|
||||
socklen_t len = sizeof(ti);
|
||||
if (0 == getsockopt(rttfd, SOL_TCP, TCP_INFO, &ti, &len)) {
|
||||
got_rtt = true;
|
||||
srtt = ti.tcpi_rtt;
|
||||
rtt_var = ti.tcpi_rttvar;
|
||||
}
|
||||
#elif defined(OS_MACOSX)
|
||||
struct tcp_connection_info ti;
|
||||
socklen_t len = sizeof(ti);
|
||||
if (0 == getsockopt(rttfd, IPPROTO_TCP, TCP_CONNECTION_INFO, &ti, &len)) {
|
||||
got_rtt = true;
|
||||
srtt = ti.tcpi_srtt;
|
||||
rtt_var = ti.tcpi_rttvar;
|
||||
}
|
||||
#endif
|
||||
char rtt_display[32];
|
||||
if (got_rtt) {
|
||||
snprintf(rtt_display, sizeof(rtt_display), "%.1f/%.1f",
|
||||
srtt / 1000.0, rtt_var / 1000.0);
|
||||
} else {
|
||||
strcpy(rtt_display, "-");
|
||||
}
|
||||
os << bar << min_width(NameOfPoint(ptr->remote_side()), 19) << bar;
|
||||
if (is_channel_conn) {
|
||||
if (ptr->local_side().port > 0) {
|
||||
os << min_width(ptr->local_side().port, 5) << bar;
|
||||
} else {
|
||||
os << min_width("-", 5) << bar;
|
||||
}
|
||||
os << min_width(ptr->recent_error_count(), 10) << bar
|
||||
<< min_width(ptr->isolated_times(), 7) << bar;
|
||||
}
|
||||
os << SSLStateToYesNo(ptr->ssl_state(), use_html) << bar;
|
||||
char protname[32];
|
||||
if (pooled_count < 0) {
|
||||
snprintf(protname, sizeof(protname), "%s", pref_prot);
|
||||
} else {
|
||||
snprintf(protname, sizeof(protname), "%s*%d", pref_prot,
|
||||
pooled_count);
|
||||
}
|
||||
os << min_width(protname, 12) << bar;
|
||||
if (ptr->fd() >= 0) {
|
||||
os << min_width(ptr->fd(), 5) << bar;
|
||||
} else {
|
||||
os << min_width("-", 5) << bar;
|
||||
}
|
||||
os << min_width(stat.in_size_s, 9) << bar
|
||||
<< min_width(stat.in_num_messages_s, 6) << bar
|
||||
<< min_width(stat.in_size_m, 10) << bar
|
||||
<< min_width(stat.in_num_messages_m, 8) << bar
|
||||
<< min_width(stat.out_size_s, 10) << bar
|
||||
<< min_width(stat.out_num_messages_s, 6) << bar
|
||||
<< min_width(stat.out_size_m, 10) << bar
|
||||
<< min_width(stat.out_num_messages_m, 8) << bar
|
||||
<< min_width(rtt_display, 11) << bar;
|
||||
}
|
||||
|
||||
if (use_html) {
|
||||
os << "<a href=\"/sockets/" << socket_id << "\">" << socket_id
|
||||
<< "</a>";
|
||||
} else {
|
||||
os << socket_id;
|
||||
}
|
||||
if (use_html) {
|
||||
os << "</td></tr>";
|
||||
}
|
||||
os << '\n';
|
||||
}
|
||||
if (use_html) {
|
||||
os << "</table>\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionsService::default_method(
|
||||
::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::ConnectionsRequest*,
|
||||
::brpc::ConnectionsResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
const Server* server = cntl->server();
|
||||
Acceptor* am = server->_am;
|
||||
Acceptor* internal_am = server->_internal_am;
|
||||
butil::IOBufBuilder os;
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
|
||||
if (use_html) {
|
||||
os << "<!DOCTYPE html><html><head>\n"
|
||||
<< gridtable_style()
|
||||
<< "<script src=\"/js/sorttable\"></script>\n"
|
||||
<< "<script language=\"javascript\" type=\"text/javascript\" src=\"/js/jquery_min\"></script>\n"
|
||||
<< TabsHead()
|
||||
<< "</head><body>";
|
||||
server->PrintTabsBody(os, "connections");
|
||||
}
|
||||
|
||||
size_t max_shown = (size_t)FLAGS_max_shown_connections;
|
||||
if (cntl->http_request().uri().GetQuery("givemeall")) {
|
||||
max_shown = std::numeric_limits<size_t>::max();
|
||||
}
|
||||
bool has_uncopied = false;
|
||||
std::vector<SocketId> conns;
|
||||
// NOTE: not accurate count.
|
||||
size_t num_conns = am->ConnectionCount();
|
||||
am->ListConnections(&conns, max_shown);
|
||||
if (conns.size() == max_shown && num_conns > conns.size()) {
|
||||
// OK to have false positive
|
||||
has_uncopied = true;
|
||||
}
|
||||
if (internal_am) {
|
||||
size_t num_conns2 = internal_am->ConnectionCount();
|
||||
std::vector<SocketId> internal_conns;
|
||||
// Connections to internal_port are generally small, thus
|
||||
// -max_shown_connections is treated as a soft limit
|
||||
internal_am->ListConnections(&internal_conns, max_shown);
|
||||
if (internal_conns.size() == max_shown &&
|
||||
num_conns2 > internal_conns.size()) {
|
||||
// OK to have false positive
|
||||
has_uncopied = true;
|
||||
}
|
||||
conns.insert(conns.end(), internal_conns.begin(), internal_conns.end());
|
||||
}
|
||||
os << "server_connection_count: " << num_conns << '\n';
|
||||
PrintConnections(os, conns, use_html, server, false/*is_channel_conn*/);
|
||||
if (has_uncopied) {
|
||||
// Notice that we don't put the link of givemeall directly because
|
||||
// people seeing the link are very likely to click it which may be
|
||||
// slow and should generally be avoided.
|
||||
os << "(Stop printing more connections... check out all connections"
|
||||
" by appending \"?givemeall\" to the url of current page)"
|
||||
<< (use_html ? "<br>\n" : "\n");
|
||||
}
|
||||
|
||||
SocketMapList(&conns);
|
||||
os << (use_html ? "<br>\n" : "\n")
|
||||
<< "channel_connection_count: " << GetChannelConnectionCount() << '\n';
|
||||
PrintConnections(os, conns, use_html, server, true/*is_channel_conn*/);
|
||||
|
||||
if (use_html) {
|
||||
os << "</body></html>\n";
|
||||
}
|
||||
|
||||
os.move_to(cntl->response_attachment());
|
||||
cntl->set_response_compress_type(COMPRESS_TYPE_GZIP);
|
||||
}
|
||||
|
||||
void ConnectionsService::GetTabInfo(TabInfoList* info_list) const {
|
||||
TabInfo* info = info_list->add();
|
||||
info->path = "/connections";
|
||||
info->tab_name = "connections";
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,47 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_CONNECTIONS_SERVICE_H
|
||||
#define BRPC_CONNECTIONS_SERVICE_H
|
||||
|
||||
#include "brpc/socket_id.h"
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
#include "brpc/builtin/tabbed.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class Acceptor;
|
||||
class ConnectionsService : public connections, public Tabbed {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::ConnectionsRequest* request,
|
||||
::brpc::ConnectionsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void GetTabInfo(TabInfoList* info_list) const;
|
||||
|
||||
private:
|
||||
void PrintConnections(std::ostream& os, const std::vector<SocketId>& conns,
|
||||
bool use_html, const Server*, bool need_local) const;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_CONNECTIONS_SERVICE_H
|
||||
@@ -0,0 +1,129 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <ostream>
|
||||
#include <dirent.h> // opendir
|
||||
#include <fcntl.h> // O_RDONLY
|
||||
#include "butil/fd_guard.h"
|
||||
#include "butil/fd_utility.h"
|
||||
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/builtin/dir_service.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
void DirService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::DirRequest*,
|
||||
::brpc::DirResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
std::string open_path;
|
||||
|
||||
const std::string& path_str =
|
||||
cntl->http_request().unresolved_path();
|
||||
if (!path_str.empty()) {
|
||||
open_path.reserve(path_str.size() + 2);
|
||||
open_path.push_back('/');
|
||||
open_path.append(path_str);
|
||||
} else {
|
||||
open_path = "/";
|
||||
}
|
||||
DIR* dir = opendir(open_path.c_str());
|
||||
if (NULL == dir) {
|
||||
butil::fd_guard fd(open(open_path.c_str(), O_RDONLY));
|
||||
if (fd < 0) {
|
||||
cntl->SetFailed(errno, "Cannot open `%s'", open_path.c_str());
|
||||
return;
|
||||
}
|
||||
butil::make_non_blocking(fd);
|
||||
butil::make_close_on_exec(fd);
|
||||
|
||||
butil::IOPortal read_portal;
|
||||
size_t total_read = 0;
|
||||
do {
|
||||
const ssize_t nr = read_portal.append_from_file_descriptor(
|
||||
fd, MAX_READ);
|
||||
if (nr < 0) {
|
||||
cntl->SetFailed(errno, "Cannot read `%s'", open_path.c_str());
|
||||
return;
|
||||
}
|
||||
if (nr == 0) {
|
||||
break;
|
||||
}
|
||||
total_read += nr;
|
||||
} while (total_read < MAX_READ);
|
||||
butil::IOBuf& resp = cntl->response_attachment();
|
||||
resp.swap(read_portal);
|
||||
if (total_read >= MAX_READ) {
|
||||
std::ostringstream oss;
|
||||
oss << " <" << lseek(fd, 0, SEEK_END) - total_read << " more bytes>";
|
||||
resp.append(oss.str());
|
||||
}
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
} else {
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
const butil::EndPoint* const html_addr = (use_html ? Path::LOCAL : NULL);
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
|
||||
std::vector<std::string> files;
|
||||
files.reserve(32);
|
||||
// readdir_r is marked as deprecated since glibc 2.24.
|
||||
#if defined(__GLIBC__) && \
|
||||
(__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 24))
|
||||
for (struct dirent* p = NULL; (p = readdir(dir)) != NULL; ) {
|
||||
#else
|
||||
struct dirent entbuf;
|
||||
for (struct dirent* p = NULL; readdir_r(dir, &entbuf, &p) == 0 && p; ) {
|
||||
#endif
|
||||
files.push_back(p->d_name);
|
||||
}
|
||||
CHECK_EQ(0, closedir(dir));
|
||||
|
||||
std::sort(files.begin(), files.end());
|
||||
butil::IOBufBuilder os;
|
||||
if (use_html) {
|
||||
os << "<!DOCTYPE html><html><body><pre>";
|
||||
}
|
||||
std::string str1;
|
||||
std::string str2;
|
||||
for (size_t i = 0; i < files.size(); ++i) {
|
||||
if (path_str.empty() && files[i] == "..") {
|
||||
// back to /index
|
||||
os << Path("", html_addr, files[i].c_str()) << '\n';
|
||||
} else {
|
||||
str1 = open_path;
|
||||
AppendFileName(&str1, files[i]);
|
||||
str2 = "/dir";
|
||||
str2.append(str1);
|
||||
os << Path(str2.c_str(), html_addr, files[i].c_str()) << '\n';
|
||||
}
|
||||
}
|
||||
if (use_html) {
|
||||
os << "</pre></body></html>";
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,39 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_DIR_SERVICE_H
|
||||
#define BRPC_DIR_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class DirService : public dir {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::DirRequest* request,
|
||||
::brpc::DirResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
|
||||
#endif // BRPC_DIR_SERVICE_H
|
||||
@@ -0,0 +1,257 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <ostream>
|
||||
#include <vector> // std::vector
|
||||
#include <set>
|
||||
#include <gflags/gflags.h> // GetAllFlags
|
||||
// CommandLineFlagInfo
|
||||
#include "butil/string_printf.h"
|
||||
#include "butil/string_splitter.h"
|
||||
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/errno.pb.h"
|
||||
#include "brpc/server.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/builtin/flags_service.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
DEFINE_bool(immutable_flags, false, "gflags on /flags page can't be modified");
|
||||
|
||||
// Replace some characters with html replacements. If the input string does
|
||||
// not need to be changed, return its const reference directly, otherwise put
|
||||
// the replaced string in backing string and return its const reference.
|
||||
// TODO(gejun): Make this function more general.
|
||||
static std::string HtmlReplace(const std::string& s) {
|
||||
std::string b;
|
||||
size_t last_pos = 0;
|
||||
while (1) {
|
||||
size_t new_pos = s.find_first_of("<>&", last_pos);
|
||||
if (new_pos == std::string::npos) {
|
||||
if (b.empty()) { // no special characters.
|
||||
return s;
|
||||
}
|
||||
b.append(s.data() + last_pos, s.size() - last_pos);
|
||||
return b;
|
||||
}
|
||||
b.append(s.data() + last_pos, new_pos - last_pos);
|
||||
switch (s[new_pos]) {
|
||||
case '<' : b.append("<"); break;
|
||||
case '>' : b.append(">"); break;
|
||||
case '&' : b.append("&"); break;
|
||||
default: b.push_back(s[new_pos]); break;
|
||||
}
|
||||
last_pos = new_pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void PrintFlag(std::ostream& os, const GFLAGS_NAMESPACE::CommandLineFlagInfo& flag,
|
||||
bool use_html) {
|
||||
if (use_html) {
|
||||
os << "<tr><td>";
|
||||
}
|
||||
os << flag.name;
|
||||
if (flag.has_validator_fn) {
|
||||
if (use_html) {
|
||||
os << " (<a href='/flags/" << flag.name
|
||||
<< "?setvalue&withform'>R</a>)";
|
||||
} else {
|
||||
os << " (R)";
|
||||
}
|
||||
}
|
||||
os << (use_html ? "</td><td>" : " | ");
|
||||
if (!flag.is_default && use_html) {
|
||||
os << "<span style='color:#FF0000'>";
|
||||
}
|
||||
if (!flag.current_value.empty()) {
|
||||
os << (use_html ? HtmlReplace(flag.current_value)
|
||||
: flag.current_value);
|
||||
} else {
|
||||
os << (use_html ? " " : " ");
|
||||
}
|
||||
if (!flag.is_default) {
|
||||
if (flag.default_value != flag.current_value) {
|
||||
os << " (default:" << (use_html ?
|
||||
HtmlReplace(flag.default_value) :
|
||||
flag.default_value) << ')';
|
||||
}
|
||||
if (use_html) {
|
||||
os << "</span>";
|
||||
}
|
||||
}
|
||||
os << (use_html ? "</td><td>" : " | ") << flag.description
|
||||
<< (use_html ? "</td><td>" : " | ") << flag.filename;
|
||||
if (use_html) {
|
||||
os << "</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
void FlagsService::set_value_page(Controller* cntl,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
const std::string& name = cntl->http_request().unresolved_path();
|
||||
GFLAGS_NAMESPACE::CommandLineFlagInfo info;
|
||||
if (!GFLAGS_NAMESPACE::GetCommandLineFlagInfo(name.c_str(), &info)) {
|
||||
cntl->SetFailed(ENOMETHOD, "No such gflag");
|
||||
return;
|
||||
}
|
||||
butil::IOBufBuilder os;
|
||||
const bool is_string = (info.type == "string");
|
||||
os << "<!DOCTYPE html><html><body>"
|
||||
"<form action='' method='get'>"
|
||||
" Set `" << name << "' from ";
|
||||
if (is_string) {
|
||||
os << '"';
|
||||
}
|
||||
os << info.current_value;
|
||||
if (is_string) {
|
||||
os << '"';
|
||||
}
|
||||
os << " to <input name='setvalue' value=''>"
|
||||
" <button>go</button>"
|
||||
"</form>"
|
||||
"</body></html>";
|
||||
os.move_to(cntl->response_attachment());
|
||||
}
|
||||
|
||||
void FlagsService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::FlagsRequest*,
|
||||
::brpc::FlagsResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
const std::string* value_str =
|
||||
cntl->http_request().uri().GetQuery(SETVALUE_STR);
|
||||
const std::string& constraint = cntl->http_request().unresolved_path();
|
||||
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
|
||||
if (value_str != NULL) {
|
||||
// reload value if ?setvalue=VALUE is present.
|
||||
if (constraint.empty()) {
|
||||
cntl->SetFailed(ENOMETHOD, "Require gflag name");
|
||||
return;
|
||||
}
|
||||
if (use_html && cntl->http_request().uri().GetQuery("withform")) {
|
||||
return set_value_page(cntl, done_guard.release());
|
||||
}
|
||||
GFLAGS_NAMESPACE::CommandLineFlagInfo info;
|
||||
if (!GFLAGS_NAMESPACE::GetCommandLineFlagInfo(constraint.c_str(), &info)) {
|
||||
cntl->SetFailed(ENOMETHOD, "No such gflag");
|
||||
return;
|
||||
}
|
||||
if (!info.has_validator_fn) {
|
||||
cntl->SetFailed(EPERM, "A reloadable gflag must have validator");
|
||||
return;
|
||||
}
|
||||
if (FLAGS_immutable_flags) {
|
||||
cntl->SetFailed(EPERM, "Cannot modify `%s' because -immutable_flags is on",
|
||||
constraint.c_str());
|
||||
return;
|
||||
}
|
||||
if (GFLAGS_NAMESPACE::SetCommandLineOption(constraint.c_str(),
|
||||
value_str->c_str()).empty()) {
|
||||
cntl->SetFailed(EPERM, "Fail to set `%s' to %s",
|
||||
constraint.c_str(),
|
||||
(value_str->empty() ? "empty string" : value_str->c_str()));
|
||||
return;
|
||||
}
|
||||
butil::IOBufBuilder os;
|
||||
os << "Set `" << constraint << "' to " << *value_str;
|
||||
if (use_html) {
|
||||
os << "<br><a href='/flags'>[back to flags]</a>";
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse query-string which is comma-separated flagnames/wildcards.
|
||||
std::vector<std::string> wildcards;
|
||||
std::set<std::string> exact;
|
||||
if (!constraint.empty()) {
|
||||
for (butil::StringMultiSplitter sp(constraint.c_str(), ",;"); sp != NULL; ++sp) {
|
||||
std::string name(sp.field(), sp.length());
|
||||
if (name.find_first_of("$*") != std::string::npos) {
|
||||
wildcards.push_back(name);
|
||||
} else {
|
||||
exact.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print header of the table
|
||||
butil::IOBufBuilder os;
|
||||
if (use_html) {
|
||||
os << "<!DOCTYPE html><html><head>\n" << gridtable_style()
|
||||
<< "<script language=\"javascript\" type=\"text/javascript\" src=\"/js/jquery_min\"></script>\n"
|
||||
<< TabsHead()
|
||||
<< "</head><body>";
|
||||
cntl->server()->PrintTabsBody(os, "flags");
|
||||
os << "<table class=\"gridtable\" border=\"1\"><tr><th>Name</th><th>Value</th>"
|
||||
"<th>Description</th><th>Defined At</th></tr>\n";
|
||||
} else {
|
||||
os << "Name | Value | Description | Defined At\n"
|
||||
"---------------------------------------\n";
|
||||
}
|
||||
|
||||
if (!constraint.empty() && wildcards.empty()) {
|
||||
// Only exact names. We don't have to iterate all flags in this case.
|
||||
for (std::set<std::string>::iterator it = exact.begin();
|
||||
it != exact.end(); ++it) {
|
||||
GFLAGS_NAMESPACE::CommandLineFlagInfo info;
|
||||
if (GFLAGS_NAMESPACE::GetCommandLineFlagInfo(it->c_str(), &info)) {
|
||||
PrintFlag(os, info, use_html);
|
||||
os << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Iterate all flags and filter.
|
||||
std::vector<GFLAGS_NAMESPACE::CommandLineFlagInfo> flag_list;
|
||||
flag_list.reserve(128);
|
||||
GFLAGS_NAMESPACE::GetAllFlags(&flag_list);
|
||||
for (std::vector<GFLAGS_NAMESPACE::CommandLineFlagInfo>::iterator
|
||||
it = flag_list.begin(); it != flag_list.end(); ++it) {
|
||||
if (!constraint.empty() &&
|
||||
exact.find(it->name) == exact.end() &&
|
||||
!MatchAnyWildcard(it->name, wildcards)) {
|
||||
continue;
|
||||
}
|
||||
PrintFlag(os, *it, use_html);
|
||||
os << '\n';
|
||||
}
|
||||
}
|
||||
if (use_html) {
|
||||
os << "</table></body></html>\n";
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
cntl->set_response_compress_type(COMPRESS_TYPE_GZIP);
|
||||
}
|
||||
|
||||
void FlagsService::GetTabInfo(TabInfoList* info_list) const {
|
||||
TabInfo* info = info_list->add();
|
||||
info->path = "/flags";
|
||||
info->tab_name = "flags";
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,46 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_FLAGS_SERVICE_H
|
||||
#define BRPC_FLAGS_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
#include "brpc/builtin/tabbed.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class FlagsService : public flags, public Tabbed {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::FlagsRequest* request,
|
||||
::brpc::FlagsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void GetTabInfo(TabInfoList* info_list) const;
|
||||
|
||||
private:
|
||||
void set_value_page(Controller* cntl, ::google::protobuf::Closure* done);
|
||||
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
|
||||
#endif // BRPC_FLAGS_SERVICE_H
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,37 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BUILTIN_FLOT_MIN_JS_H
|
||||
#define BRPC_BUILTIN_FLOT_MIN_JS_H
|
||||
|
||||
#include "butil/iobuf.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Get the flot.min.js as string or IOBuf.
|
||||
// We need to pack all js inside C++ code so that builtin services can be
|
||||
// accessed without external resources and network connection.
|
||||
const char* flot_min_js();
|
||||
const butil::IOBuf& flot_min_js_iobuf();
|
||||
const butil::IOBuf& flot_min_js_iobuf_gzip();
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_BUILTIN_FLOT_MIN_JS_H
|
||||
@@ -0,0 +1,79 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include "butil/macros.h" // ARRAY_SIZE
|
||||
#include "butil/iobuf.h" // butil::IOBuf
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/builtin/get_favicon_service.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
static unsigned char s_favicon_array[] = {
|
||||
71, 73, 70, 56, 57, 97, 16, 0, 16, 0, 241, 0, 0, 0, 0, 0, 153, 153, 153, 255,
|
||||
255, 255, 0, 0, 0, 33, 249, 4, 9, 50, 0, 3, 0, 33, 255, 11, 78, 69, 84, 83, 67,
|
||||
65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0, 44, 0, 0, 0, 0, 16, 0, 16, 0, 0, 2, 231,
|
||||
4, 0, 0, 0, 0, 0, 0, 0, 0, 132, 16, 66, 8, 33, 132, 16, 4, 65, 0, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 64, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 64, 0, 0, 16, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 2, 0, 64, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 2, 0, 64, 0, 0,
|
||||
16, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 64, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0,
|
||||
0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 10, 0, 0, 33, 249, 4, 9, 50, 0, 3, 0, 44, 0, 0, 0, 0, 16, 0, 16,
|
||||
0, 0, 2, 231, 4, 0, 0, 0, 0, 0, 0, 0, 0, 132, 16, 66, 8, 33, 132, 16, 4, 65, 0,
|
||||
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 64,
|
||||
0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 64, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 2,
|
||||
0, 64, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 64, 0, 32, 0, 0, 0, 2, 129, 0, 0,
|
||||
0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16,
|
||||
0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16,
|
||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 59
|
||||
};
|
||||
|
||||
static pthread_once_t s_favicon_buf_once = PTHREAD_ONCE_INIT;
|
||||
static butil::IOBuf* s_favicon_buf = NULL;
|
||||
static void InitFavIcon() {
|
||||
s_favicon_buf = new butil::IOBuf;
|
||||
s_favicon_buf->append((const void *)s_favicon_array,
|
||||
arraysize(s_favicon_array));
|
||||
}
|
||||
static const butil::IOBuf& GetFavIcon() {
|
||||
pthread_once(&s_favicon_buf_once, InitFavIcon);
|
||||
return *s_favicon_buf;
|
||||
}
|
||||
|
||||
void GetFaviconService::default_method(
|
||||
::google::protobuf::RpcController* controller,
|
||||
const GetFaviconRequest* /*request*/,
|
||||
GetFaviconResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
Controller *cntl = (Controller*)controller;
|
||||
cntl->http_response().set_content_type("image/x-icon");
|
||||
cntl->response_attachment().clear();
|
||||
cntl->response_attachment().append(GetFavIcon());
|
||||
if (done) {
|
||||
done->Run();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,37 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_GET_FAVICON_SERVICE_H
|
||||
#define BRPC_GET_FAVICON_SERVICE_H
|
||||
|
||||
#include "brpc/get_favicon.pb.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class GetFaviconService : public ico {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* controller,
|
||||
const GetFaviconRequest* request,
|
||||
GetFaviconResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_GET_FAVICON_SERVICE_H
|
||||
@@ -0,0 +1,132 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#include "butil/macros.h" // ARRAY_SIZE
|
||||
#include "butil/iobuf.h" // butil::IOBuf
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/builtin/sorttable_js.h"
|
||||
#include "brpc/builtin/jquery_min_js.h"
|
||||
#include "brpc/builtin/flot_min_js.h"
|
||||
#include "brpc/builtin/viz_min_js.h"
|
||||
#include "brpc/builtin/get_js_service.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
static const char* g_last_modified = "Wed, 16 Sep 2015 01:25:30 GMT";
|
||||
|
||||
static void SetExpires(HttpHeader* header, time_t seconds) {
|
||||
char buf[256];
|
||||
time_t now = time(0);
|
||||
Time2GMT(now, buf, sizeof(buf));
|
||||
header->SetHeader("Date", buf);
|
||||
Time2GMT(now + seconds, buf, sizeof(buf));
|
||||
header->SetHeader("Expires", buf);
|
||||
}
|
||||
|
||||
void GetJsService::sorttable(
|
||||
::google::protobuf::RpcController* controller,
|
||||
const GetJsRequest* /*request*/,
|
||||
GetJsResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = (Controller*)controller;
|
||||
cntl->http_response().set_content_type("application/javascript");
|
||||
SetExpires(&cntl->http_response(), 80000);
|
||||
cntl->response_attachment().append(sorttable_js_iobuf());
|
||||
}
|
||||
|
||||
void GetJsService::jquery_min(
|
||||
::google::protobuf::RpcController* controller,
|
||||
const GetJsRequest* /*request*/,
|
||||
GetJsResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = (Controller*)controller;
|
||||
cntl->http_response().set_content_type("application/javascript");
|
||||
SetExpires(&cntl->http_response(), 600);
|
||||
|
||||
const std::string* ims =
|
||||
cntl->http_request().GetHeader("If-Modified-Since");
|
||||
if (ims != NULL && *ims == g_last_modified) {
|
||||
cntl->http_response().set_status_code(HTTP_STATUS_NOT_MODIFIED);
|
||||
return;
|
||||
}
|
||||
cntl->http_response().SetHeader("Last-Modified", g_last_modified);
|
||||
|
||||
if (SupportGzip(cntl)) {
|
||||
cntl->http_response().SetHeader("Content-Encoding", "gzip");
|
||||
cntl->response_attachment().append(jquery_min_js_iobuf_gzip());
|
||||
} else {
|
||||
cntl->response_attachment().append(jquery_min_js_iobuf());
|
||||
}
|
||||
}
|
||||
|
||||
void GetJsService::flot_min(
|
||||
::google::protobuf::RpcController* controller,
|
||||
const GetJsRequest* /*request*/,
|
||||
GetJsResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = (Controller*)controller;
|
||||
cntl->http_response().set_content_type("application/javascript");
|
||||
SetExpires(&cntl->http_response(), 80000);
|
||||
|
||||
const std::string* ims =
|
||||
cntl->http_request().GetHeader("If-Modified-Since");
|
||||
if (ims != NULL && *ims == g_last_modified) {
|
||||
cntl->http_response().set_status_code(HTTP_STATUS_NOT_MODIFIED);
|
||||
return;
|
||||
}
|
||||
cntl->http_response().SetHeader("Last-Modified", g_last_modified);
|
||||
|
||||
if (SupportGzip(cntl)) {
|
||||
cntl->http_response().SetHeader("Content-Encoding", "gzip");
|
||||
cntl->response_attachment().append(flot_min_js_iobuf_gzip());
|
||||
} else {
|
||||
cntl->response_attachment().append(flot_min_js_iobuf());
|
||||
}
|
||||
}
|
||||
|
||||
void GetJsService::viz_min(
|
||||
::google::protobuf::RpcController* controller,
|
||||
const GetJsRequest* /*request*/,
|
||||
GetJsResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = (Controller*)controller;
|
||||
cntl->http_response().set_content_type("application/javascript");
|
||||
SetExpires(&cntl->http_response(), 80000);
|
||||
|
||||
const std::string* ims =
|
||||
cntl->http_request().GetHeader("If-Modified-Since");
|
||||
if (ims != NULL && *ims == g_last_modified) {
|
||||
cntl->http_response().set_status_code(HTTP_STATUS_NOT_MODIFIED);
|
||||
return;
|
||||
}
|
||||
cntl->http_response().SetHeader("Last-Modified", g_last_modified);
|
||||
|
||||
if (SupportGzip(cntl)) {
|
||||
cntl->http_response().SetHeader("Content-Encoding", "gzip");
|
||||
cntl->response_attachment().append(viz_min_js_iobuf_gzip());
|
||||
} else {
|
||||
cntl->response_attachment().append(viz_min_js_iobuf());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,56 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_GET_JAVASCRIPT_SERVICE_H
|
||||
#define BRPC_GET_JAVASCRIPT_SERVICE_H
|
||||
|
||||
#include "brpc/get_js.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Get packed js.
|
||||
// "/js/sorttable" : http://www.kryogenix.org/code/browser/sorttable/
|
||||
// "/js/jquery_min" : jquery 1.8.3
|
||||
// "/js/flot_min" : plotting library for jquery.
|
||||
class GetJsService : public ::brpc::js {
|
||||
public:
|
||||
void sorttable(::google::protobuf::RpcController* controller,
|
||||
const GetJsRequest* request,
|
||||
GetJsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void jquery_min(::google::protobuf::RpcController* controller,
|
||||
const GetJsRequest* request,
|
||||
GetJsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void flot_min(::google::protobuf::RpcController* controller,
|
||||
const GetJsRequest* request,
|
||||
GetJsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void viz_min(::google::protobuf::RpcController* controller,
|
||||
const GetJsRequest* request,
|
||||
GetJsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_GET_JAVASCRIPT_SERVICE_H
|
||||
@@ -0,0 +1,41 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/builtin/grpc_health_check_service.h"
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/server.h" // Server
|
||||
|
||||
namespace brpc {
|
||||
void GrpcHealthCheckService::Check(::google::protobuf::RpcController* cntl_base,
|
||||
const grpc::health::v1::HealthCheckRequest* request,
|
||||
grpc::health::v1::HealthCheckResponse* response,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
const Server* server = cntl->server();
|
||||
if (server->options().health_reporter) {
|
||||
server->options().health_reporter->GenerateReport(
|
||||
cntl, done_guard.release());
|
||||
} else {
|
||||
response->set_status(grpc::health::v1::HealthCheckResponse_ServingStatus_SERVING);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_GRPC_HEALTH_CHECK_SERVICE_H
|
||||
#define BRPC_GRPC_HEALTH_CHECK_SERVICE_H
|
||||
|
||||
#include "brpc/grpc_health_check.pb.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class GrpcHealthCheckService : public grpc::health::v1::Health {
|
||||
public:
|
||||
void Check(::google::protobuf::RpcController* cntl_base,
|
||||
const grpc::health::v1::HealthCheckRequest* request,
|
||||
grpc::health::v1::HealthCheckResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif //BRPC_GRPC_HEALTH_CHECK_SERVICE_H
|
||||
@@ -0,0 +1,44 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/server.h" // Server
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/builtin/health_service.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
void HealthService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HealthRequest*,
|
||||
::brpc::HealthResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
const Server* server = cntl->server();
|
||||
if (server->options().health_reporter) {
|
||||
server->options().health_reporter->GenerateReport(
|
||||
cntl, done_guard.release());
|
||||
} else {
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
cntl->response_attachment().append("OK");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,39 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_HEALTH_SERVICE_H
|
||||
#define BRPC_HEALTH_SERVICE_H
|
||||
|
||||
#include <ostream>
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class HealthService : public health {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HealthRequest* request,
|
||||
::brpc::HealthResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif //BRPC_HEALTH_SERVICE_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,90 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_HOTSPOTS_SERVICE_H
|
||||
#define BRPC_HOTSPOTS_SERVICE_H
|
||||
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
#include "brpc/builtin/tabbed.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class Server;
|
||||
|
||||
class HotspotsService : public hotspots, public Tabbed {
|
||||
public:
|
||||
void cpu(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HotspotsRequest* request,
|
||||
::brpc::HotspotsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void heap(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HotspotsRequest* request,
|
||||
::brpc::HotspotsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void growth(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HotspotsRequest* request,
|
||||
::brpc::HotspotsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void contention(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HotspotsRequest* request,
|
||||
::brpc::HotspotsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void iobuf(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HotspotsRequest* request,
|
||||
::brpc::HotspotsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void cpu_non_responsive(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HotspotsRequest* request,
|
||||
::brpc::HotspotsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void heap_non_responsive(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HotspotsRequest* request,
|
||||
::brpc::HotspotsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void growth_non_responsive(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HotspotsRequest* request,
|
||||
::brpc::HotspotsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void contention_non_responsive(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HotspotsRequest* request,
|
||||
::brpc::HotspotsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void iobuf_non_responsive(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::HotspotsRequest* request,
|
||||
::brpc::HotspotsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void GetTabInfo(brpc::TabInfoList*) const;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
|
||||
#endif // BRPC_HOTSPOTS_SERVICE_H
|
||||
@@ -0,0 +1,60 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <ostream>
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/builtin/ids_service.h"
|
||||
|
||||
namespace bthread {
|
||||
void id_status(bthread_id_t id, std::ostream& os);
|
||||
void id_pool_status(std::ostream& os);
|
||||
}
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
void IdsService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::IdsRequest*,
|
||||
::brpc::IdsResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
butil::IOBufBuilder os;
|
||||
const std::string& constraint = cntl->http_request().unresolved_path();
|
||||
|
||||
if (constraint.empty()) {
|
||||
os << "# Use /ids/<call_id>\n";
|
||||
bthread::id_pool_status(os);
|
||||
} else {
|
||||
char* endptr = NULL;
|
||||
bthread_id_t id = { strtoull(constraint.c_str(), &endptr, 10) };
|
||||
if (*endptr == '\0' || *endptr == '/') {
|
||||
bthread::id_status(id, os);
|
||||
} else {
|
||||
cntl->SetFailed(ENOMETHOD, "path=%s is not a bthread_id",
|
||||
constraint.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,38 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_IDS_SERVICE_H
|
||||
#define BRPC_IDS_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class IdsService: public ids {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::IdsRequest* request,
|
||||
::brpc::IdsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_IDS_SERVICE_H
|
||||
@@ -0,0 +1,176 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <gflags/gflags.h> // DECLARE_xxx
|
||||
#include <google/protobuf/descriptor.h>
|
||||
#include "butil/time.h" // gettimeofday_us
|
||||
#include "brpc/server.h" // Server
|
||||
#include "brpc/builtin/index_service.h"
|
||||
#include "brpc/builtin/status_service.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/details/tcmalloc_extension.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
void IndexService::GetTabInfo(TabInfoList* info_list) const {
|
||||
TabInfo* info = info_list->add();
|
||||
info->path = "/index?as_more";
|
||||
info->tab_name = "more";
|
||||
}
|
||||
|
||||
DECLARE_bool(enable_rpcz);
|
||||
DECLARE_bool(enable_dir_service);
|
||||
DECLARE_bool(enable_threads_service);
|
||||
|
||||
// Set in ProfilerLinker.
|
||||
bool cpu_profiler_enabled = false;
|
||||
|
||||
void IndexService::default_method(::google::protobuf::RpcController* controller,
|
||||
const IndexRequest*,
|
||||
IndexResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = (Controller*)controller;
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
const Server* server = cntl->server();
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
const bool as_more = cntl->http_request().uri().GetQuery("as_more");
|
||||
if (use_html && !as_more) {
|
||||
google::protobuf::Service* svc = server->FindServiceByFullName(
|
||||
StatusService::descriptor()->full_name());
|
||||
StatusService* st_svc = dynamic_cast<StatusService*>(svc);
|
||||
if (st_svc == NULL) {
|
||||
cntl->SetFailed("Fail to find StatusService");
|
||||
return;
|
||||
}
|
||||
return st_svc->default_method(cntl, NULL, NULL, done_guard.release());
|
||||
}
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
const butil::EndPoint* const html_addr = (use_html ? Path::LOCAL : NULL);
|
||||
const char* const NL = (use_html ? "<br>\n" : "\n");
|
||||
const char* const SP = (use_html ? " " : " ");
|
||||
|
||||
butil::IOBufBuilder os;
|
||||
if (use_html) {
|
||||
os << "<!DOCTYPE html><html>";
|
||||
if (as_more) {
|
||||
os << "<head>\n"
|
||||
"<script language=\"javascript\" type=\"text/javascript\" src=\"/js/jquery_min\"></script>\n"
|
||||
<< TabsHead()
|
||||
<< "</head>\n";
|
||||
}
|
||||
os << "<body>\n";
|
||||
if (as_more) {
|
||||
cntl->server()->PrintTabsBody(os, "more");
|
||||
};
|
||||
os << "<pre>";
|
||||
}
|
||||
os << logo();
|
||||
if (use_html) {
|
||||
os << "</pre>";
|
||||
}
|
||||
os << '\n';
|
||||
if (use_html) {
|
||||
os << "<a href=\"https://github.com/apache/brpc\">github</a>";
|
||||
} else {
|
||||
os << "github : https://github.com/apache/brpc";
|
||||
}
|
||||
os << NL << NL;
|
||||
if (!as_more) {
|
||||
os << Path("/status", html_addr) << " : Status of services" << NL
|
||||
<< Path("/connections", html_addr) << " : List all connections" << NL
|
||||
<< Path("/flags", html_addr) << " : List all gflags" << NL
|
||||
<< SP << Path("/flags/port", html_addr) << " : List the gflag" << NL
|
||||
<< SP << Path("/flags/guard_page_size;help*", html_addr)
|
||||
<< " : List multiple gflags with glob patterns"
|
||||
" (Use $ instead of ? to match single character)" << NL << SP
|
||||
<< "/flags/NAME?setvalue=VALUE : Change a gflag, validator will be called."
|
||||
" User is responsible for thread-safety and consistency issues." << NL
|
||||
|
||||
<< Path("/vars", html_addr) << " : List all exposed bvars" << NL
|
||||
<< SP << Path("/vars/rpc_num_sockets", html_addr)
|
||||
<< " : List the bvar" << NL
|
||||
<< SP << Path("/vars/rpc_server*_count;iobuf_blo$k_*", html_addr)
|
||||
<< " : List multiple bvars with glob patterns"
|
||||
" (Use $ instead of ? to match single character)" << NL
|
||||
|
||||
<< Path("/rpcz", html_addr) << " : Recent RPC calls"
|
||||
<< (!FLAGS_enable_rpcz ? "(disabled)" : "") << NL
|
||||
<< SP << Path("/rpcz/stats", html_addr) << " : Statistics of rpcz" << NL;
|
||||
|
||||
std::ostringstream tmp_oss;
|
||||
const int64_t seconds_before = butil::gettimeofday_us() - 30 * 1000000L;
|
||||
tmp_oss << "/rpcz?" << TIME_STR << '=';
|
||||
PrintRealDateTime(tmp_oss, seconds_before, true);
|
||||
os << SP << Path(tmp_oss.str().c_str(), html_addr)
|
||||
<< " : RPC calls before the time" << NL;
|
||||
tmp_oss.str("");
|
||||
tmp_oss << "/rpcz?" << TIME_STR << '=';
|
||||
PrintRealDateTime(tmp_oss, seconds_before, true);
|
||||
tmp_oss << '&' << MAX_SCAN_STR << "=10";
|
||||
os << SP << Path(tmp_oss.str().c_str(), html_addr)
|
||||
<< " : N RPC calls at most before the time" << NL << SP
|
||||
<< "Other filters: " << MIN_LATENCY_STR << ", " << MIN_REQUEST_SIZE_STR
|
||||
<< ", " << MIN_RESPONSE_SIZE_STR << ", " << LOG_ID_STR
|
||||
<< ", " << ERROR_CODE_STR << NL
|
||||
<< SP << "/rpcz?" << TRACE_ID_STR
|
||||
<< "=N : Recent RPC calls whose trace_id is N" << NL
|
||||
<< SP << "/rpcz?" << TRACE_ID_STR << "=N&" << SPAN_ID_STR
|
||||
<< "=M : Recent RPC calls whose trace_id is N and span_id is M" << NL
|
||||
|
||||
<< Path("/hotspots/cpu", html_addr) << " : Profiling CPU"
|
||||
<< (!cpu_profiler_enabled ? " (disabled)" : "") << NL
|
||||
<< Path("/hotspots/heap", html_addr) << " : Profiling heap"
|
||||
<< (!IsHeapProfilerEnabled() ? " (disabled)" : "") << NL
|
||||
<< Path("/hotspots/growth", html_addr)
|
||||
<< " : Profiling growth of heap"
|
||||
<< (!IsHeapProfilerEnabled() ? " (disabled)" : "") << NL
|
||||
<< Path("/hotspots/contention", html_addr)
|
||||
<< " : Profiling contention of lock" << NL;
|
||||
}
|
||||
os << "curl -H 'Content-Type: application/json' -d 'JSON' ";
|
||||
if (butil::is_endpoint_extended(server->listen_address())) {
|
||||
os << "<listen_address>";
|
||||
} else {
|
||||
const butil::EndPoint my_addr(butil::my_ip(), server->listen_address().port);
|
||||
os << my_addr;
|
||||
}
|
||||
os << "/ServiceName/MethodName : Call method by http+json" << NL
|
||||
|
||||
<< Path("/version", html_addr)
|
||||
<< " : Version of this server, set by Server::set_version()" << NL
|
||||
<< Path("/health", html_addr) << " : Test healthy" << NL
|
||||
<< Path("/vlog", html_addr) << " : List all VLOG callsites" << NL
|
||||
<< Path("/sockets", html_addr) << " : Check status of a Socket" << NL
|
||||
<< Path("/bthreads", html_addr) << " : Check status of a bthread or all living bthreads" << NL
|
||||
<< Path("/ids", html_addr) << " : Check status of a bthread_id" << NL
|
||||
<< Path("/protobufs", html_addr) << " : List all protobuf services and messages" << NL
|
||||
<< Path("/list", html_addr) << " : json signature of methods" << NL
|
||||
<< Path("/threads", html_addr) << " : Check pstack"
|
||||
<< (!FLAGS_enable_threads_service ? " (disabled)" : "") << NL
|
||||
<< Path("/dir", html_addr) << " : Browse directories and files"
|
||||
<< (!FLAGS_enable_dir_service ? " (disabled)" : "") << NL
|
||||
<< Path("/memory", html_addr) << " : Get malloc allocator information" << NL;
|
||||
if (use_html) {
|
||||
os << "</body></html>";
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,42 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_INDEX_SERVICE_H
|
||||
#define BRPC_INDEX_SERVICE_H
|
||||
|
||||
#include <ostream>
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
#include "brpc/builtin/tabbed.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class IndexService : public index, public Tabbed {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::IndexRequest* request,
|
||||
::brpc::IndexResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void GetTabInfo(brpc::TabInfoList*) const;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif //BRPC_INDEX_SERVICE_H
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,37 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BUILTIN_JQUERY_MIN_JS_H
|
||||
#define BRPC_BUILTIN_JQUERY_MIN_JS_H
|
||||
|
||||
#include "butil/iobuf.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Get the jquery.min.js as string or IOBuf.
|
||||
// We need to pack all js inside C++ code so that builtin services can be
|
||||
// accessed without external resources and network connection.
|
||||
const char* jquery_min_js();
|
||||
const butil::IOBuf& jquery_min_js_iobuf();
|
||||
const butil::IOBuf& jquery_min_js_iobuf_gzip();
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_BUILTIN_JQUERY_MIN_JS_H
|
||||
@@ -0,0 +1,42 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <vector> // std::vector
|
||||
#include <google/protobuf/descriptor.h> // ServiceDescriptor
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/server.h" // Server
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/builtin/list_service.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
void ListService::default_method(::google::protobuf::RpcController*,
|
||||
const ::brpc::ListRequest*,
|
||||
::brpc::ListResponse* response,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
std::vector<google::protobuf::Service*> services;
|
||||
_server->ListServices(&services);
|
||||
for (size_t i = 0; i < services.size(); ++i) {
|
||||
google::protobuf::ServiceDescriptorProto *svc = response->add_service();
|
||||
services[i]->GetDescriptor()->CopyTo(svc);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,44 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_LIST_SERVICE_H
|
||||
#define BRPC_LIST_SERVICE_H
|
||||
|
||||
#include <ostream>
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class Server;
|
||||
|
||||
class ListService : public list {
|
||||
public:
|
||||
explicit ListService(Server* server) : _server(server) {}
|
||||
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::ListRequest* request,
|
||||
::brpc::ListResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
private:
|
||||
Server* _server;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif //BRPC_LIST_SERVICE_H
|
||||
@@ -0,0 +1,93 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#include "butil/time.h"
|
||||
#include "butil/logging.h"
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/builtin/memory_service.h"
|
||||
#include "brpc/details/tcmalloc_extension.h"
|
||||
#include "brpc/details/jemalloc_profiler.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
DEFINE_int32(max_tc_stats_buf_len, 32 * 1024, "max length of TCMalloc stats");
|
||||
BRPC_VALIDATE_GFLAG(max_tc_stats_buf_len, PositiveInteger);
|
||||
|
||||
static inline void get_tcmalloc_num_prop(MallocExtension* malloc_ext,
|
||||
const char* prop_name,
|
||||
butil::IOBufBuilder& os) {
|
||||
size_t value;
|
||||
if (malloc_ext->GetNumericProperty(prop_name, &value)) {
|
||||
os << prop_name << ": " << value << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
static void get_tcmalloc_memory_info(butil::IOBuf& out) {
|
||||
MallocExtension* malloc_ext = MallocExtension::instance();
|
||||
butil::IOBufBuilder os;
|
||||
os << "------------------------------------------------\n";
|
||||
get_tcmalloc_num_prop(malloc_ext, "generic.total_physical_bytes", os);
|
||||
get_tcmalloc_num_prop(malloc_ext, "generic.current_allocated_bytes", os);
|
||||
get_tcmalloc_num_prop(malloc_ext, "generic.heap_size", os);
|
||||
get_tcmalloc_num_prop(malloc_ext, "tcmalloc.current_total_thread_cache_bytes", os);
|
||||
get_tcmalloc_num_prop(malloc_ext, "tcmalloc.central_cache_free_bytes", os);
|
||||
get_tcmalloc_num_prop(malloc_ext, "tcmalloc.transfer_cache_free_bytes", os);
|
||||
get_tcmalloc_num_prop(malloc_ext, "tcmalloc.thread_cache_free_bytes", os);
|
||||
get_tcmalloc_num_prop(malloc_ext, "tcmalloc.pageheap_free_bytes", os);
|
||||
get_tcmalloc_num_prop(malloc_ext, "tcmalloc.pageheap_unmapped_bytes", os);
|
||||
|
||||
int32_t len = FLAGS_max_tc_stats_buf_len;
|
||||
std::unique_ptr<char[]> buf(new char[len]);
|
||||
malloc_ext->GetStats(buf.get(), len);
|
||||
os << buf.get();
|
||||
|
||||
os.move_to(out);
|
||||
}
|
||||
|
||||
static void get_jemalloc_memory_info(Controller* cntl) {
|
||||
const brpc::URI& uri = cntl->http_request().uri();
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
|
||||
const std::string* uri_opts = uri.GetQuery("opts");
|
||||
std::string opts = !uri_opts || uri_opts->empty() ? "Ja" : *uri_opts;
|
||||
cntl->response_attachment().append(StatsPrint(opts));
|
||||
}
|
||||
|
||||
void MemoryService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::MemoryRequest*,
|
||||
::brpc::MemoryResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
auto cntl = static_cast<Controller*>(cntl_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
butil::IOBuf& resp = cntl->response_attachment();
|
||||
|
||||
if (IsTCMallocEnabled()) {
|
||||
butil::IOBufBuilder os;
|
||||
get_tcmalloc_memory_info(resp);
|
||||
} else if (HasJemalloc()) {
|
||||
// support ip:port/memory?opts=Ja
|
||||
get_jemalloc_memory_info(cntl);
|
||||
} else {
|
||||
resp.append("tcmalloc or jemalloc is not enabled");
|
||||
cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,36 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_MALLOC_SERVICE_H
|
||||
#define BRPC_MALLOC_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class MemoryService : public memory {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::MemoryRequest* request,
|
||||
::brpc::MemoryResponse* response,
|
||||
::google::protobuf::Closure* done) override;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_MALLOC_SERVICE_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BUILTIN_PPROF_PERL_H
|
||||
#define BRPC_BUILTIN_PPROF_PERL_H
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
const char* pprof_perl();
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_BUILTIN_PPROF_PERL_H
|
||||
@@ -0,0 +1,579 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#include <time.h> // strftime
|
||||
#include <pthread.h>
|
||||
#include <map>
|
||||
#include <limits>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h> // O_RDONLY
|
||||
#include "butil/string_printf.h" // string_printf
|
||||
#include "butil/string_splitter.h" // StringSplitter
|
||||
#include "butil/file_util.h" // butil::FilePath
|
||||
#include "butil/files/scoped_file.h" // ScopedFILE
|
||||
#include "butil/time.h"
|
||||
#include "butil/popen.h" // butil::read_command_output
|
||||
#include "butil/process_util.h" // butil::ReadCommandLine
|
||||
#include "brpc/log.h"
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/builtin/pprof_service.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/details/tcmalloc_extension.h"
|
||||
#include "brpc/details/jemalloc_profiler.h"
|
||||
#include "bthread/bthread.h" // bthread_usleep
|
||||
#include "butil/fd_guard.h"
|
||||
|
||||
extern "C" {
|
||||
#if defined(OS_LINUX)
|
||||
extern char *program_invocation_name;
|
||||
#endif
|
||||
int BAIDU_WEAK ProfilerStart(const char* fname);
|
||||
void BAIDU_WEAK ProfilerStop();
|
||||
}
|
||||
|
||||
namespace bthread {
|
||||
bool ContentionProfilerStart(const char* filename);
|
||||
void ContentionProfilerStop();
|
||||
}
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
static int ReadSeconds(Controller* cntl) {
|
||||
int seconds = 0;
|
||||
const std::string* param =
|
||||
cntl->http_request().uri().GetQuery("seconds");
|
||||
if (param != NULL) {
|
||||
char* endptr = NULL;
|
||||
const long sec = strtol(param->c_str(), &endptr, 10);
|
||||
if (endptr == param->c_str() + param->length()) {
|
||||
seconds = sec;
|
||||
} else {
|
||||
cntl->SetFailed(EINVAL, "Invalid seconds=%s", param->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return seconds;
|
||||
}
|
||||
|
||||
int MakeProfName(ProfilingType type, char* buf, size_t buf_len) {
|
||||
// Add pprof_ prefix to separate from /hotspots
|
||||
int nr = snprintf(buf, buf_len, "%s/pprof_%s/", FLAGS_rpc_profiling_dir.c_str(),
|
||||
GetProgramChecksum());
|
||||
if (nr < 0) {
|
||||
return -1;
|
||||
}
|
||||
buf += nr;
|
||||
buf_len -= nr;
|
||||
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
struct tm* timeinfo = localtime(&rawtime);
|
||||
const size_t nw = strftime(buf, buf_len, "%Y%m%d.%H%M%S", timeinfo);
|
||||
buf += nw;
|
||||
buf_len -= nw;
|
||||
|
||||
// We have checksum in the path, getpid() is not necessary now.
|
||||
snprintf(buf, buf_len, ".%s", ProfilingType2String(type));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PProfService::profile(
|
||||
::google::protobuf::RpcController* controller_base,
|
||||
const ::brpc::ProfileRequest* /*request*/,
|
||||
::brpc::ProfileResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller* cntl = static_cast<Controller*>(controller_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
if ((void*)ProfilerStart == NULL || (void*)ProfilerStop == NULL) {
|
||||
cntl->SetFailed(ENOMETHOD, "%s, to enable cpu profiler, check out "
|
||||
"docs/cn/cpu_profiler.md",
|
||||
berror(ENOMETHOD));
|
||||
return;
|
||||
}
|
||||
int sleep_sec = ReadSeconds(cntl);
|
||||
if (sleep_sec <= 0) {
|
||||
if (!cntl->Failed()) {
|
||||
cntl->SetFailed(EINVAL, "You have to specify ?seconds=N. If you're "
|
||||
"using pprof, add --seconds=N");
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Log requester
|
||||
std::ostringstream client_info;
|
||||
client_info << cntl->remote_side();
|
||||
if (cntl->auth_context()) {
|
||||
client_info << "(auth=" << cntl->auth_context()->user() << ')';
|
||||
} else {
|
||||
client_info << "(no auth)";
|
||||
}
|
||||
LOG(INFO) << client_info.str() << " requests for cpu profile for "
|
||||
<< sleep_sec << " seconds";
|
||||
|
||||
char prof_name[256];
|
||||
if (MakeProfName(PROFILING_CPU, prof_name, sizeof(prof_name)) != 0) {
|
||||
cntl->SetFailed(errno, "Fail to create .prof file, %s", berror());
|
||||
return;
|
||||
}
|
||||
butil::File::Error error;
|
||||
const butil::FilePath dir = butil::FilePath(prof_name).DirName();
|
||||
if (!butil::CreateDirectoryAndGetError(dir, &error)) {
|
||||
cntl->SetFailed(EPERM, "Fail to create directory=`%s'",dir.value().c_str());
|
||||
return;
|
||||
}
|
||||
if (!ProfilerStart(prof_name)) {
|
||||
cntl->SetFailed(EAGAIN, "Another profiler is running, try again later");
|
||||
return;
|
||||
}
|
||||
if (bthread_usleep(sleep_sec * 1000000L) != 0) {
|
||||
PLOG(WARNING) << "Profiling has been interrupted";
|
||||
}
|
||||
ProfilerStop();
|
||||
|
||||
butil::fd_guard fd(open(prof_name, O_RDONLY));
|
||||
if (fd < 0) {
|
||||
cntl->SetFailed(ENOENT, "Fail to open %s", prof_name);
|
||||
return;
|
||||
}
|
||||
butil::IOPortal portal;
|
||||
portal.append_from_file_descriptor(fd, ULONG_MAX);
|
||||
cntl->response_attachment().swap(portal);
|
||||
}
|
||||
|
||||
void PProfService::contention(
|
||||
::google::protobuf::RpcController* controller_base,
|
||||
const ::brpc::ProfileRequest* /*request*/,
|
||||
::brpc::ProfileResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller* cntl = static_cast<Controller*>(controller_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
int sleep_sec = ReadSeconds(cntl);
|
||||
if (sleep_sec <= 0) {
|
||||
if (!cntl->Failed()) {
|
||||
cntl->SetFailed(EINVAL, "You have to specify ?seconds=N. If you're "
|
||||
"using pprof, add --seconds=N");
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Log requester
|
||||
std::ostringstream client_info;
|
||||
client_info << cntl->remote_side();
|
||||
if (cntl->auth_context()) {
|
||||
client_info << "(auth=" << cntl->auth_context()->user() << ')';
|
||||
} else {
|
||||
client_info << "(no auth)";
|
||||
}
|
||||
LOG(INFO) << client_info.str() << " requests for contention profile for "
|
||||
<< sleep_sec << " seconds";
|
||||
|
||||
char prof_name[256];
|
||||
if (MakeProfName(PROFILING_CONTENTION, prof_name, sizeof(prof_name)) != 0) {
|
||||
cntl->SetFailed(errno, "Fail to create .prof file, %s", berror());
|
||||
return;
|
||||
}
|
||||
if (!bthread::ContentionProfilerStart(prof_name)) {
|
||||
cntl->SetFailed(EAGAIN, "Another profiler is running, try again later");
|
||||
return;
|
||||
}
|
||||
if (bthread_usleep(sleep_sec * 1000000L) != 0) {
|
||||
PLOG(WARNING) << "Profiling has been interrupted";
|
||||
}
|
||||
bthread::ContentionProfilerStop();
|
||||
|
||||
butil::fd_guard fd(open(prof_name, O_RDONLY));
|
||||
if (fd < 0) {
|
||||
cntl->SetFailed(ENOENT, "Fail to open %s", prof_name);
|
||||
return;
|
||||
}
|
||||
butil::IOPortal portal;
|
||||
portal.append_from_file_descriptor(fd, ULONG_MAX);
|
||||
cntl->response_attachment().swap(portal);
|
||||
}
|
||||
|
||||
void PProfService::heap(
|
||||
::google::protobuf::RpcController* controller_base,
|
||||
const ::brpc::ProfileRequest* /*request*/,
|
||||
::brpc::ProfileResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller* cntl = static_cast<Controller*>(controller_base);
|
||||
|
||||
if (HasJemalloc()) {
|
||||
JeControlProfile(cntl);
|
||||
return;
|
||||
}
|
||||
|
||||
MallocExtension* malloc_ext = MallocExtension::instance();
|
||||
if (malloc_ext == NULL || !has_TCMALLOC_SAMPLE_PARAMETER()) {
|
||||
const char* extra_desc = "";
|
||||
if (malloc_ext != NULL) {
|
||||
extra_desc = " (no TCMALLOC_SAMPLE_PARAMETER in env)";
|
||||
}
|
||||
cntl->SetFailed(ENOMETHOD, "Heap profiler is not enabled%s,"
|
||||
"check out https://github.com/apache/brpc/blob/master/docs/cn/heap_profiler.md",
|
||||
extra_desc);
|
||||
return;
|
||||
}
|
||||
// Log requester
|
||||
std::ostringstream client_info;
|
||||
client_info << cntl->remote_side();
|
||||
if (cntl->auth_context()) {
|
||||
client_info << "(auth=" << cntl->auth_context()->user() << ')';
|
||||
} else {
|
||||
client_info << "(no auth)";
|
||||
}
|
||||
LOG(INFO) << client_info.str() << " requests for heap profile";
|
||||
|
||||
std::string obj;
|
||||
malloc_ext->GetHeapSample(&obj);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
cntl->response_attachment().append(obj);
|
||||
}
|
||||
|
||||
void PProfService::growth(
|
||||
::google::protobuf::RpcController* controller_base,
|
||||
const ::brpc::ProfileRequest* /*request*/,
|
||||
::brpc::ProfileResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller* cntl = static_cast<Controller*>(controller_base);
|
||||
MallocExtension* malloc_ext = MallocExtension::instance();
|
||||
if (malloc_ext == NULL) {
|
||||
cntl->SetFailed(ENOMETHOD, "%s, to enable growth profiler, check out "
|
||||
"docs/cn/heap_profiler.md",
|
||||
berror(ENOMETHOD));
|
||||
return;
|
||||
}
|
||||
// Log requester
|
||||
std::ostringstream client_info;
|
||||
client_info << cntl->remote_side();
|
||||
if (cntl->auth_context()) {
|
||||
client_info << "(auth=" << cntl->auth_context()->user() << ')';
|
||||
} else {
|
||||
client_info << "(no auth)";
|
||||
}
|
||||
LOG(INFO) << client_info.str() << " requests for growth profile";
|
||||
|
||||
std::string obj;
|
||||
malloc_ext->GetHeapGrowthStacks(&obj);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
cntl->response_attachment().append(obj);
|
||||
}
|
||||
|
||||
typedef std::map<uintptr_t, std::string> SymbolMap;
|
||||
struct LibInfo {
|
||||
uintptr_t start_addr;
|
||||
uintptr_t end_addr;
|
||||
size_t offset;
|
||||
std::string path;
|
||||
};
|
||||
static SymbolMap symbol_map;
|
||||
static pthread_once_t s_load_symbolmap_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
static bool HasExt(const std::string& name, const std::string& ext) {
|
||||
size_t index = name.find(ext);
|
||||
if (index == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
return (index + ext.size() == name.size() ||
|
||||
name[index + ext.size()] == '.');
|
||||
}
|
||||
|
||||
static int ExtractSymbolsFromBinary(
|
||||
std::map<uintptr_t, std::string>& addr_map,
|
||||
const LibInfo& lib_info) {
|
||||
butil::Timer tm;
|
||||
tm.start();
|
||||
std::string cmd = "nm -C -p ";
|
||||
cmd.append(lib_info.path);
|
||||
std::stringstream ss;
|
||||
const int rc = butil::read_command_output(ss, cmd.c_str());
|
||||
if (rc < 0) {
|
||||
LOG(ERROR) << "Fail to popen `" << cmd << "'";
|
||||
return -1;
|
||||
}
|
||||
std::string line;
|
||||
while (std::getline(ss, line)) {
|
||||
butil::StringSplitter sp(line.c_str(), ' ');
|
||||
if (sp == NULL) {
|
||||
continue;
|
||||
}
|
||||
char* endptr = NULL;
|
||||
uintptr_t addr = strtoull(sp.field(), &endptr, 16);
|
||||
if (*endptr != ' ') {
|
||||
continue;
|
||||
}
|
||||
if (addr < lib_info.start_addr) {
|
||||
addr = addr + lib_info.start_addr - lib_info.offset;
|
||||
}
|
||||
if (addr >= lib_info.end_addr) {
|
||||
continue;
|
||||
}
|
||||
++sp;
|
||||
if (sp == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (sp.length() != 1UL) {
|
||||
continue;
|
||||
}
|
||||
//const char c = *sp.field();
|
||||
|
||||
++sp;
|
||||
if (sp == NULL) {
|
||||
continue;
|
||||
}
|
||||
const char* name_begin = sp.field();
|
||||
if (strncmp(name_begin, "typeinfo ", 9) == 0 ||
|
||||
strncmp(name_begin, "VTT ", 4) == 0 ||
|
||||
strncmp(name_begin, "vtable ", 7) == 0 ||
|
||||
strncmp(name_begin, "global ", 7) == 0 ||
|
||||
strncmp(name_begin, "guard ", 6) == 0) {
|
||||
addr_map[addr] = std::string();
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* name_end = sp.field();
|
||||
bool stop = false;
|
||||
char last_char = '\0';
|
||||
while (1) {
|
||||
switch (*name_end) {
|
||||
case 0:
|
||||
case '\r':
|
||||
case '\n':
|
||||
stop = true;
|
||||
break;
|
||||
case '(':
|
||||
case '<':
|
||||
// \(.*\w\)[(<]... -> \1
|
||||
// foo(..) -> foo
|
||||
// foo<...>(...) -> foo
|
||||
// a::b::foo(...) -> a::b::foo
|
||||
// a::(b)::foo(...) -> a::(b)::foo
|
||||
if (isalpha(last_char) || isdigit(last_char) ||
|
||||
last_char == '_') {
|
||||
stop = true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (stop) {
|
||||
break;
|
||||
}
|
||||
last_char = *name_end++;
|
||||
}
|
||||
// If address conflicts, choose a shorter name (not necessarily to be
|
||||
// T type in nm). This works fine because aliases often have more
|
||||
// prefixes.
|
||||
const size_t name_len = name_end - name_begin;
|
||||
SymbolMap::iterator it = addr_map.find(addr);
|
||||
if (it != addr_map.end()) {
|
||||
if (name_len < it->second.size()) {
|
||||
it->second.assign(name_begin, name_len);
|
||||
}
|
||||
} else {
|
||||
addr_map[addr] = std::string(name_begin, name_len);
|
||||
}
|
||||
}
|
||||
if (addr_map.find(lib_info.end_addr) == addr_map.end()) {
|
||||
addr_map[lib_info.end_addr] = std::string();
|
||||
}
|
||||
tm.stop();
|
||||
RPC_VLOG << "Loaded " << lib_info.path << " in " << tm.m_elapsed() << "ms";
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void LoadSymbols() {
|
||||
butil::Timer tm;
|
||||
tm.start();
|
||||
butil::ScopedFILE fp(fopen("/proc/self/maps", "r"));
|
||||
if (fp == NULL) {
|
||||
return;
|
||||
}
|
||||
char* line = NULL;
|
||||
size_t line_len = 0;
|
||||
ssize_t nr = 0;
|
||||
while ((nr = getline(&line, &line_len, fp.get())) != -1) {
|
||||
butil::StringSplitter sp(line, line + nr, ' ');
|
||||
if (sp == NULL) {
|
||||
continue;
|
||||
}
|
||||
char* endptr;
|
||||
uintptr_t start_addr = strtoull(sp.field(), &endptr, 16);
|
||||
if (*endptr != '-') {
|
||||
continue;
|
||||
}
|
||||
++endptr;
|
||||
uintptr_t end_addr = strtoull(endptr, &endptr, 16);
|
||||
if (*endptr != ' ') {
|
||||
continue;
|
||||
}
|
||||
++sp;
|
||||
// ..x. must be executable
|
||||
if (sp == NULL || sp.length() != 4 || sp.field()[2] != 'x') {
|
||||
continue;
|
||||
}
|
||||
++sp;
|
||||
if (sp == NULL) {
|
||||
continue;
|
||||
}
|
||||
size_t offset = strtoull(sp.field(), &endptr, 16);
|
||||
if (*endptr != ' ') {
|
||||
continue;
|
||||
}
|
||||
//skip $4~$5
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
++sp;
|
||||
}
|
||||
if (sp == NULL) {
|
||||
continue;
|
||||
}
|
||||
size_t n = sp.length();
|
||||
if (sp.field()[n-1] == '\n') {
|
||||
--n;
|
||||
}
|
||||
std::string path(sp.field(), n);
|
||||
if (!HasExt(path, ".so") && !HasExt(path, ".dll") &&
|
||||
!HasExt(path, ".dylib") && !HasExt(path, ".bundle")) {
|
||||
continue;
|
||||
}
|
||||
LibInfo info;
|
||||
info.start_addr = start_addr;
|
||||
info.end_addr = end_addr;
|
||||
info.offset = offset;
|
||||
info.path = path;
|
||||
ExtractSymbolsFromBinary(symbol_map, info);
|
||||
}
|
||||
free(line);
|
||||
|
||||
LibInfo info;
|
||||
info.start_addr = 0;
|
||||
info.end_addr = std::numeric_limits<uintptr_t>::max();
|
||||
info.offset = 0;
|
||||
#if defined(OS_LINUX)
|
||||
info.path = program_invocation_name;
|
||||
#elif defined(OS_MACOSX)
|
||||
info.path = getprogname();
|
||||
#endif
|
||||
ExtractSymbolsFromBinary(symbol_map, info);
|
||||
|
||||
butil::Timer tm2;
|
||||
tm2.start();
|
||||
size_t num_removed = 0;
|
||||
bool last_is_empty = false;
|
||||
for (SymbolMap::iterator
|
||||
it = symbol_map.begin(); it != symbol_map.end();) {
|
||||
if (it->second.empty()) {
|
||||
if (last_is_empty) {
|
||||
symbol_map.erase(it++);
|
||||
++num_removed;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
last_is_empty = true;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
tm2.stop();
|
||||
RPC_VLOG_IF(num_removed) << "Removed " << num_removed << " entries in "
|
||||
<< tm2.m_elapsed() << "ms";
|
||||
|
||||
tm.stop();
|
||||
RPC_VLOG << "Loaded all symbols in " << tm.m_elapsed() << "ms";
|
||||
}
|
||||
|
||||
static void FindSymbols(butil::IOBuf* out, std::vector<uintptr_t>& addr_list) {
|
||||
char buf[32];
|
||||
for (size_t i = 0; i < addr_list.size(); ++i) {
|
||||
int len = snprintf(buf, sizeof(buf), "0x%08lx\t", addr_list[i]);
|
||||
out->append(buf, len);
|
||||
SymbolMap::const_iterator it = symbol_map.lower_bound(addr_list[i]);
|
||||
if (it == symbol_map.end() || it->first != addr_list[i]) {
|
||||
if (it != symbol_map.begin()) {
|
||||
--it;
|
||||
} else {
|
||||
len = snprintf(buf, sizeof(buf), "0x%08lx\n", addr_list[i]);
|
||||
out->append(buf, len);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (it->second.empty()) {
|
||||
len = snprintf(buf, sizeof(buf), "0x%08lx\n", addr_list[i]);
|
||||
out->append(buf, len);
|
||||
} else {
|
||||
out->append(it->second);
|
||||
out->push_back('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PProfService::symbol(
|
||||
::google::protobuf::RpcController* controller_base,
|
||||
const ::brpc::ProfileRequest* /*request*/,
|
||||
::brpc::ProfileResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller* cntl = static_cast<Controller*>(controller_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
|
||||
// Load /proc/self/maps
|
||||
pthread_once(&s_load_symbolmap_once, LoadSymbols);
|
||||
|
||||
if (cntl->http_request().method() != HTTP_METHOD_POST) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "num_symbols: %lu\n", symbol_map.size());
|
||||
cntl->response_attachment().append(buf);
|
||||
} else {
|
||||
// addr_str is addressed separated by +
|
||||
std::string addr_str = cntl->request_attachment().to_string();
|
||||
// May be quoted
|
||||
const char* addr_cstr = addr_str.c_str();
|
||||
if (*addr_cstr == '\'' || *addr_cstr == '"') {
|
||||
++addr_cstr;
|
||||
}
|
||||
std::vector<uintptr_t> addr_list;
|
||||
addr_list.reserve(32);
|
||||
butil::StringSplitter sp(addr_cstr, '+');
|
||||
for ( ; sp != NULL; ++sp) {
|
||||
char* endptr;
|
||||
uintptr_t addr = strtoull(sp.field(), &endptr, 16);
|
||||
addr_list.push_back(addr);
|
||||
}
|
||||
FindSymbols(&cntl->response_attachment(), addr_list);
|
||||
}
|
||||
}
|
||||
|
||||
void PProfService::cmdline(::google::protobuf::RpcController* controller_base,
|
||||
const ::brpc::ProfileRequest* /*request*/,
|
||||
::brpc::ProfileResponse* /*response*/,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller* cntl = static_cast<Controller*>(controller_base);
|
||||
cntl->http_response().set_content_type("text/plain" /*FIXME*/);
|
||||
char buf[1024]; // should be enough?
|
||||
const ssize_t nr = butil::ReadCommandLine(buf, sizeof(buf), true);
|
||||
if (nr < 0) {
|
||||
cntl->SetFailed(ENOENT, "Fail to read cmdline");
|
||||
return;
|
||||
}
|
||||
cntl->response_attachment().append(buf, nr);
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,62 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_PPROF_SERVICE_H
|
||||
#define BRPC_PPROF_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class PProfService : public pprof {
|
||||
public:
|
||||
void profile(::google::protobuf::RpcController* controller,
|
||||
const ::brpc::ProfileRequest* request,
|
||||
::brpc::ProfileResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void contention(::google::protobuf::RpcController* controller,
|
||||
const ::brpc::ProfileRequest* request,
|
||||
::brpc::ProfileResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void heap(::google::protobuf::RpcController* controller,
|
||||
const ::brpc::ProfileRequest* request,
|
||||
::brpc::ProfileResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void growth(::google::protobuf::RpcController* controller,
|
||||
const ::brpc::ProfileRequest* request,
|
||||
::brpc::ProfileResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void symbol(::google::protobuf::RpcController* controller,
|
||||
const ::brpc::ProfileRequest* request,
|
||||
::brpc::ProfileResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void cmdline(::google::protobuf::RpcController* controller,
|
||||
const ::brpc::ProfileRequest* request,
|
||||
::brpc::ProfileResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif //BRPC_PPROF_SERVICE_H
|
||||
@@ -0,0 +1,244 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/server.h" // Server
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/builtin/prometheus_metrics_service.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "bvar/bvar.h"
|
||||
|
||||
namespace bvar {
|
||||
DECLARE_int32(bvar_latency_p1);
|
||||
DECLARE_int32(bvar_latency_p2);
|
||||
DECLARE_int32(bvar_latency_p3);
|
||||
DECLARE_int32(bvar_max_dump_multi_dimension_metric_number);
|
||||
}
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Defined in server.cpp
|
||||
extern const char* const g_server_info_prefix;
|
||||
|
||||
// This is a class that convert bvar result to prometheus output.
|
||||
// Currently the output only includes gauge and summary for two
|
||||
// reasons:
|
||||
// 1) We cannot tell gauge and counter just from name and what's
|
||||
// more counter is just another gauge.
|
||||
// 2) Histogram and summary is equivalent except that histogram
|
||||
// calculates quantiles in the server side.
|
||||
class PrometheusMetricsDumper : public bvar::Dumper {
|
||||
public:
|
||||
explicit PrometheusMetricsDumper(butil::IOBufBuilder* os,
|
||||
const std::string& server_prefix)
|
||||
: _os(os)
|
||||
, _server_prefix(server_prefix) {
|
||||
}
|
||||
|
||||
bool dump(const std::string& name, const butil::StringPiece& desc) override;
|
||||
bool dump_mvar(const std::string& name, const butil::StringPiece& desc) override;
|
||||
bool dump_comment(const std::string& name, const std::string& type) override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(PrometheusMetricsDumper);
|
||||
|
||||
// Return true iff name ends with suffix output by LatencyRecorder.
|
||||
bool DumpLatencyRecorderSuffix(const butil::StringPiece& name,
|
||||
const butil::StringPiece& desc);
|
||||
|
||||
// 6 is the number of bvars in LatencyRecorder that indicating percentiles
|
||||
static const int NPERCENTILES = 6;
|
||||
|
||||
struct SummaryItems {
|
||||
std::string latency_percentiles[NPERCENTILES];
|
||||
int64_t latency_avg;
|
||||
int64_t count;
|
||||
std::string metric_name;
|
||||
|
||||
bool IsComplete() const { return !metric_name.empty(); }
|
||||
};
|
||||
const SummaryItems* ProcessLatencyRecorderSuffix(const butil::StringPiece& name,
|
||||
const butil::StringPiece& desc);
|
||||
|
||||
private:
|
||||
butil::IOBufBuilder* _os;
|
||||
const std::string _server_prefix;
|
||||
std::map<std::string, SummaryItems> _m;
|
||||
};
|
||||
|
||||
butil::StringPiece GetMetricsName(const std::string& name) {
|
||||
auto pos = name.find_first_of('{');
|
||||
int size = (pos == std::string::npos) ? name.size() : pos;
|
||||
return butil::StringPiece(name.data(), size);
|
||||
}
|
||||
|
||||
bool PrometheusMetricsDumper::dump(const std::string& name,
|
||||
const butil::StringPiece& desc) {
|
||||
if (!desc.empty() && desc[0] == '"') {
|
||||
// there is no necessary to monitor string in prometheus
|
||||
return true;
|
||||
}
|
||||
if (DumpLatencyRecorderSuffix(name, desc)) {
|
||||
// Has encountered name with suffix exposed by LatencyRecorder,
|
||||
// Leave it to DumpLatencyRecorderSuffix to output Summary.
|
||||
return true;
|
||||
}
|
||||
|
||||
auto metrics_name = GetMetricsName(name);
|
||||
|
||||
*_os << "# HELP " << metrics_name << '\n'
|
||||
<< "# TYPE " << metrics_name << " gauge" << '\n'
|
||||
<< name << " " << desc << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PrometheusMetricsDumper::dump_mvar(const std::string& name, const butil::StringPiece& desc) {
|
||||
if (!desc.empty() && desc[0] == '"') {
|
||||
// there is no necessary to monitor string in prometheus
|
||||
return true;
|
||||
}
|
||||
*_os << name << " " << desc << "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PrometheusMetricsDumper::dump_comment(const std::string& name, const std::string& type) {
|
||||
*_os << "# HELP " << name << '\n'
|
||||
<< "# TYPE " << name << " " << type << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
const PrometheusMetricsDumper::SummaryItems*
|
||||
PrometheusMetricsDumper::ProcessLatencyRecorderSuffix(const butil::StringPiece& name,
|
||||
const butil::StringPiece& desc) {
|
||||
static std::string latency_names[] = {
|
||||
butil::string_printf("_latency_%d", (int)bvar::FLAGS_bvar_latency_p1),
|
||||
butil::string_printf("_latency_%d", (int)bvar::FLAGS_bvar_latency_p2),
|
||||
butil::string_printf("_latency_%d", (int)bvar::FLAGS_bvar_latency_p3),
|
||||
"_latency_999", "_latency_9999", "_max_latency"
|
||||
};
|
||||
CHECK(NPERCENTILES == arraysize(latency_names));
|
||||
const std::string desc_str = desc.as_string();
|
||||
butil::StringPiece metric_name(name);
|
||||
for (int i = 0; i < NPERCENTILES; ++i) {
|
||||
if (!metric_name.ends_with(latency_names[i])) {
|
||||
continue;
|
||||
}
|
||||
metric_name.remove_suffix(latency_names[i].size());
|
||||
SummaryItems* si = &_m[metric_name.as_string()];
|
||||
si->latency_percentiles[i] = desc_str;
|
||||
if (i == NPERCENTILES - 1) {
|
||||
// '_max_latency' is the last suffix name that appear in the sorted bvar
|
||||
// list, which means all related percentiles have been gathered and we are
|
||||
// ready to output a Summary.
|
||||
si->metric_name = metric_name.as_string();
|
||||
}
|
||||
return si;
|
||||
}
|
||||
// Get the average of latency in recent window size
|
||||
if (metric_name.ends_with("_latency")) {
|
||||
metric_name.remove_suffix(8);
|
||||
SummaryItems* si = &_m[metric_name.as_string()];
|
||||
si->latency_avg = strtoll(desc_str.data(), NULL, 10);
|
||||
return si;
|
||||
}
|
||||
if (metric_name.ends_with("_count")) {
|
||||
metric_name.remove_suffix(6);
|
||||
SummaryItems* si = &_m[metric_name.as_string()];
|
||||
si->count = strtoll(desc_str.data(), NULL, 10);
|
||||
return si;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool PrometheusMetricsDumper::DumpLatencyRecorderSuffix(
|
||||
const butil::StringPiece& name,
|
||||
const butil::StringPiece& desc) {
|
||||
if (!name.starts_with(_server_prefix)) {
|
||||
return false;
|
||||
}
|
||||
const SummaryItems* si = ProcessLatencyRecorderSuffix(name, desc);
|
||||
if (!si) {
|
||||
return false;
|
||||
}
|
||||
if (!si->IsComplete()) {
|
||||
return true;
|
||||
}
|
||||
*_os << "# HELP " << si->metric_name << '\n'
|
||||
<< "# TYPE " << si->metric_name << " summary\n"
|
||||
<< si->metric_name << "{quantile=\""
|
||||
<< (double)(bvar::FLAGS_bvar_latency_p1) / 100 << "\"} "
|
||||
<< si->latency_percentiles[0] << '\n'
|
||||
<< si->metric_name << "{quantile=\""
|
||||
<< (double)(bvar::FLAGS_bvar_latency_p2) / 100 << "\"} "
|
||||
<< si->latency_percentiles[1] << '\n'
|
||||
<< si->metric_name << "{quantile=\""
|
||||
<< (double)(bvar::FLAGS_bvar_latency_p3) / 100 << "\"} "
|
||||
<< si->latency_percentiles[2] << '\n'
|
||||
<< si->metric_name << "{quantile=\"0.999\"} "
|
||||
<< si->latency_percentiles[3] << '\n'
|
||||
<< si->metric_name << "{quantile=\"0.9999\"} "
|
||||
<< si->latency_percentiles[4] << '\n'
|
||||
<< si->metric_name << "{quantile=\"1\"} "
|
||||
<< si->latency_percentiles[5] << '\n'
|
||||
<< si->metric_name << "{quantile=\"avg\"} "
|
||||
<< si->latency_avg << '\n'
|
||||
<< si->metric_name << "_sum "
|
||||
// There is no sum of latency in bvar output, just use
|
||||
// average * count as approximation
|
||||
<< si->latency_avg * si->count << '\n'
|
||||
<< si->metric_name << "_count " << si->count << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrometheusMetricsService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::MetricsRequest*,
|
||||
::brpc::MetricsResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
if (DumpPrometheusMetricsToIOBuf(&cntl->response_attachment()) != 0) {
|
||||
cntl->SetFailed("Fail to dump metrics");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int DumpPrometheusMetricsToIOBuf(butil::IOBuf* output) {
|
||||
butil::IOBufBuilder os;
|
||||
PrometheusMetricsDumper dumper(&os, g_server_info_prefix);
|
||||
const int ndump = bvar::Variable::dump_exposed(&dumper, NULL);
|
||||
if (ndump < 0) {
|
||||
return -1;
|
||||
}
|
||||
os.move_to(*output);
|
||||
|
||||
if (bvar::FLAGS_bvar_max_dump_multi_dimension_metric_number > 0) {
|
||||
PrometheusMetricsDumper dumper_md(&os, g_server_info_prefix);
|
||||
const int ndump_md = bvar::MVariableBase::dump_exposed(&dumper_md, NULL);
|
||||
if (ndump_md < 0) {
|
||||
return -1;
|
||||
}
|
||||
output->append(butil::IOBuf::Movable(os.buf()));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,39 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_PROMETHEUS_METRICS_SERVICE_H
|
||||
#define BRPC_PROMETHEUS_METRICS_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class PrometheusMetricsService : public brpc_metrics {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::MetricsRequest* request,
|
||||
::brpc::MetricsResponse* response,
|
||||
::google::protobuf::Closure* done) override;
|
||||
};
|
||||
|
||||
butil::StringPiece GetMetricsName(const std::string& name);
|
||||
int DumpPrometheusMetricsToIOBuf(butil::IOBuf* output);
|
||||
|
||||
} // namepace brpc
|
||||
|
||||
#endif // BRPC_PROMETHEUS_METRICS_SERVICE_H
|
||||
@@ -0,0 +1,118 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <google/protobuf/descriptor.h> // ServiceDescriptor
|
||||
|
||||
#include "brpc/builtin/protobufs_service.h"
|
||||
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/server.h" // Server
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/details/method_status.h"// MethodStatus
|
||||
#include "brpc/builtin/common.h"
|
||||
|
||||
#include "butil/strings/string_util.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
ProtobufsService::ProtobufsService(Server* server) : _server(server) {
|
||||
CHECK_EQ(0, Init());
|
||||
}
|
||||
|
||||
int ProtobufsService::Init() {
|
||||
Server::ServiceMap &services = _server->_fullname_service_map;
|
||||
std::vector<const google::protobuf::Descriptor*> stack;
|
||||
stack.reserve(services.size() * 3);
|
||||
for (Server::ServiceMap::iterator
|
||||
iter = services.begin(); iter != services.end(); ++iter) {
|
||||
if (!iter->second.is_user_service()) {
|
||||
continue;
|
||||
}
|
||||
const google::protobuf::ServiceDescriptor* d =
|
||||
iter->second.service->GetDescriptor();
|
||||
_map[butil::EnsureString(d->full_name())] = d->DebugString();
|
||||
const int method_count = d->method_count();
|
||||
for (int j = 0; j < method_count; ++j) {
|
||||
const google::protobuf::MethodDescriptor* md = d->method(j);
|
||||
stack.push_back(md->input_type());
|
||||
stack.push_back(md->output_type());
|
||||
}
|
||||
}
|
||||
while (!stack.empty()) {
|
||||
const google::protobuf::Descriptor* d = stack.back();
|
||||
stack.pop_back();
|
||||
_map[butil::EnsureString(d->full_name())] = d->DebugString();
|
||||
for (int i = 0; i < d->field_count(); ++i) {
|
||||
const google::protobuf::FieldDescriptor* f = d->field(i);
|
||||
if (f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE ||
|
||||
f->type() == google::protobuf::FieldDescriptor::TYPE_GROUP) {
|
||||
const google::protobuf::Descriptor* sub_d = f->message_type();
|
||||
if (sub_d != d && _map.find(butil::EnsureString(sub_d->full_name())) == _map.end()) {
|
||||
stack.push_back(sub_d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ProtobufsService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ProtobufsRequest*,
|
||||
ProtobufsResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
butil::IOBufBuilder os;
|
||||
const std::string& filter = cntl->http_request().unresolved_path();
|
||||
if (filter.empty()) {
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
if (use_html) {
|
||||
os << "<!DOCTYPE html><html><head></head><body>\n";
|
||||
}
|
||||
// list all structures.
|
||||
for (Map::iterator it = _map.begin(); it != _map.end(); ++it) {
|
||||
if (use_html) {
|
||||
os << "<p><a href=\"/protobufs/" << it->first << "\">";
|
||||
}
|
||||
os << it->first;
|
||||
if (use_html) {
|
||||
os << "</a></p>";
|
||||
}
|
||||
os << '\n';
|
||||
}
|
||||
if (use_html) {
|
||||
os << "</body></html>";
|
||||
}
|
||||
} else {
|
||||
// already text.
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
Map::iterator it = _map.find(filter);
|
||||
if (it == _map.end()) {
|
||||
cntl->SetFailed(ENOMETHOD,
|
||||
"Fail to find any protobuf message by `%s'",
|
||||
filter.c_str());
|
||||
return;
|
||||
}
|
||||
os << it->second;
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,53 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_PROTOBUFS_SERVICE_H
|
||||
#define BRPC_PROTOBUFS_SERVICE_H
|
||||
|
||||
#include <ostream>
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class Server;
|
||||
|
||||
// Show DebugString of protobuf messages used in the server.
|
||||
// /protobufs : list all supported messages.
|
||||
// /protobufs/<msg>/ : Show DebugString() of <msg>
|
||||
|
||||
class ProtobufsService : public protobufs {
|
||||
public:
|
||||
explicit ProtobufsService(Server* server);
|
||||
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::ProtobufsRequest* request,
|
||||
::brpc::ProtobufsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
private:
|
||||
int Init();
|
||||
|
||||
Server* _server;
|
||||
typedef std::map<std::string, std::string> Map;
|
||||
Map _map;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif //BRPC_PROTOBUFS_SERVICE_H
|
||||
@@ -0,0 +1,742 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <ostream>
|
||||
#include <iomanip>
|
||||
#include <gflags/gflags.h>
|
||||
#include "butil/string_printf.h"
|
||||
#include "butil/string_splitter.h"
|
||||
#include "butil/macros.h"
|
||||
#include "butil/time.h"
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/server.h"
|
||||
#include "brpc/errno.pb.h"
|
||||
#include "brpc/span.h"
|
||||
#include "brpc/builtin/rpcz_service.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Defined in span.cpp
|
||||
bool has_span_db();
|
||||
|
||||
DEFINE_bool(enable_rpcz, false, "Turn on rpcz");
|
||||
BRPC_VALIDATE_GFLAG(enable_rpcz, PassValidate);
|
||||
|
||||
DEFINE_bool(rpcz_hex_log_id, false, "Show log_id in hexadecimal");
|
||||
BRPC_VALIDATE_GFLAG(rpcz_hex_log_id, PassValidate);
|
||||
|
||||
struct Hex {
|
||||
explicit Hex(uint64_t val) : _val(val) {}
|
||||
uint64_t _val;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const Hex& h) {
|
||||
const std::ios::fmtflags old_flags =
|
||||
os.setf(std::ios::hex, std::ios::basefield);
|
||||
os << h._val;
|
||||
os.flags(old_flags);
|
||||
return os;
|
||||
}
|
||||
|
||||
void RpczService::enable(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest*,
|
||||
::brpc::RpczResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
if (!GFLAGS_NAMESPACE::SetCommandLineOption("enable_rpcz", "true").empty()) {
|
||||
if (use_html) {
|
||||
// Redirect to /rpcz
|
||||
cntl->response_attachment().append(
|
||||
"<!DOCTYPE html><html><head>"
|
||||
"<meta http-equiv=\"refresh\" content=\"0; url=/rpcz\" />"
|
||||
"</head><body>");
|
||||
}
|
||||
cntl->response_attachment().append("rpcz is enabled");
|
||||
} else {
|
||||
if (use_html) {
|
||||
cntl->response_attachment().append("<!DOCTYPE html><html><body>");
|
||||
}
|
||||
cntl->response_attachment().append("Fail to set --enable_rpcz");
|
||||
}
|
||||
if (use_html) {
|
||||
cntl->response_attachment().append("</body></html>");
|
||||
}
|
||||
}
|
||||
|
||||
void RpczService::disable(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest*,
|
||||
::brpc::RpczResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
if (!GFLAGS_NAMESPACE::SetCommandLineOption("enable_rpcz", "false").empty()) {
|
||||
if (use_html) {
|
||||
// Redirect to /rpcz
|
||||
cntl->response_attachment().append(
|
||||
"<!DOCTYPE html><html><head>"
|
||||
"<meta http-equiv=\"refresh\" content=\"0; url=/rpcz\" />"
|
||||
"</head><body>");
|
||||
}
|
||||
cntl->response_attachment().append("rpcz is disabled");
|
||||
} else {
|
||||
if (use_html) {
|
||||
cntl->response_attachment().append("<!DOCTYPE html><html><body>");
|
||||
}
|
||||
cntl->response_attachment().append("Fail to set --enable_rpcz");
|
||||
}
|
||||
if (use_html) {
|
||||
cntl->response_attachment().append("</body></html>");
|
||||
}
|
||||
}
|
||||
|
||||
void RpczService::hex_log_id(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest*,
|
||||
::brpc::RpczResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
FLAGS_rpcz_hex_log_id = true;
|
||||
cntl->response_attachment().append("log_id is hexadecimal");
|
||||
}
|
||||
|
||||
void RpczService::dec_log_id(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest*,
|
||||
::brpc::RpczResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
FLAGS_rpcz_hex_log_id = false;
|
||||
cntl->response_attachment().append("log_id is decimal");
|
||||
}
|
||||
|
||||
void RpczService::stats(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest*,
|
||||
::brpc::RpczResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
|
||||
if (!FLAGS_enable_rpcz && !has_span_db()) {
|
||||
cntl->response_attachment().append(
|
||||
"rpcz is not enabled yet. You can turn on/off rpcz by accessing "
|
||||
"/rpcz/enable and /rpcz/disable respectively");
|
||||
return;
|
||||
}
|
||||
|
||||
butil::IOBufBuilder os;
|
||||
DescribeSpanDB(os);
|
||||
os.move_to(cntl->response_attachment());
|
||||
}
|
||||
|
||||
inline void PrintRealTime(std::ostream& os, int64_t tm) {
|
||||
char buf[16];
|
||||
const time_t tm_s = tm / 1000000L;
|
||||
struct tm lt;
|
||||
strftime(buf, sizeof(buf), "%H:%M:%S.", localtime_r(&tm_s, <));
|
||||
const char old_fill = os.fill('0');
|
||||
os << buf << std::setw(6) << tm % 1000000L;
|
||||
os.fill(old_fill);
|
||||
}
|
||||
|
||||
static void PrintElapse(std::ostream& os, int64_t cur_time,
|
||||
int64_t* last_time) {
|
||||
const int64_t elp = cur_time - *last_time;
|
||||
*last_time = cur_time;
|
||||
if (elp < 0) {
|
||||
os << std::fixed << std::setw(11) << std::setprecision(6)
|
||||
<< elp / 1000000.0;
|
||||
} else {
|
||||
if (elp >= 1000000L) {
|
||||
os << std::setw(4) << elp / 1000000L << '.';
|
||||
} else {
|
||||
os << " .";
|
||||
}
|
||||
os << std::setw(6) << (elp % 1000000L);
|
||||
}
|
||||
}
|
||||
|
||||
static void PrintAnnotations(
|
||||
std::ostream& os, int64_t cur_time, int64_t* last_time,
|
||||
SpanInfoExtractor** extractors, int num_extr, const RpczSpan* span) {
|
||||
int64_t anno_time;
|
||||
std::string a;
|
||||
const char* span_type_str = "Span";
|
||||
if (span) {
|
||||
switch (span->type()) {
|
||||
case SPAN_TYPE_SERVER:
|
||||
span_type_str = "ServerSpan";
|
||||
break;
|
||||
case SPAN_TYPE_CLIENT:
|
||||
span_type_str = "ClientSpan";
|
||||
break;
|
||||
case SPAN_TYPE_BTHREAD:
|
||||
span_type_str = "BthreadSpan";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Going through all extractors is not strictly correct because
|
||||
// later extractors may have earlier annotations.
|
||||
for (int i = 0; i < num_extr; ++i) {
|
||||
while (extractors[i]->PopAnnotation(cur_time, &anno_time, &a)) {
|
||||
PrintRealTime(os, anno_time);
|
||||
PrintElapse(os, anno_time, last_time);
|
||||
os << ' ';
|
||||
if (span) {
|
||||
const char* short_type = "SPAN";
|
||||
if (span->type() == SPAN_TYPE_SERVER) {
|
||||
short_type = "Server";
|
||||
} else if (span->type() == SPAN_TYPE_CLIENT) {
|
||||
short_type = "Client";
|
||||
} else if (span->type() == SPAN_TYPE_BTHREAD) {
|
||||
short_type = "Bthread";
|
||||
}
|
||||
os << '[' << short_type << " SPAN#" << Hex(span->span_id()) << "] ";
|
||||
}
|
||||
os << WebEscape(a);
|
||||
if (a.empty() || butil::back_char(a) != '\n') {
|
||||
os << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool PrintAnnotationsAndRealTimeSpan(
|
||||
std::ostream& os, int64_t cur_time, int64_t* last_time,
|
||||
SpanInfoExtractor** extr, int num_extr, const RpczSpan* span) {
|
||||
if (cur_time == 0) {
|
||||
// the field was not set.
|
||||
return false;
|
||||
}
|
||||
PrintAnnotations(os, cur_time, last_time, extr, num_extr, span);
|
||||
PrintRealTime(os, cur_time);
|
||||
PrintElapse(os, cur_time, last_time);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline int64_t GetStartRealTime(const RpczSpan& span) {
|
||||
return span.type() == SPAN_TYPE_SERVER ?
|
||||
span.received_real_us() : span.start_send_real_us();
|
||||
}
|
||||
|
||||
struct CompareByStartRealTime {
|
||||
bool operator()(const RpczSpan& s1, const RpczSpan& s2) const {
|
||||
return GetStartRealTime(s1) < GetStartRealTime(s2);
|
||||
}
|
||||
};
|
||||
|
||||
static butil::ip_t loopback_ip = butil::IP_ANY;
|
||||
static int ALLOW_UNUSED init_loopback_ip_dummy = butil::str2ip("127.0.0.1", &loopback_ip);
|
||||
|
||||
static void PrintClientSpan(
|
||||
std::ostream& os, const RpczSpan& span,
|
||||
int64_t* last_time, SpanInfoExtractor* server_extr, bool use_html) {
|
||||
SpanInfoExtractor client_extr(span.info().c_str());
|
||||
int num_extr = 0;
|
||||
SpanInfoExtractor* extr[2];
|
||||
if (server_extr) {
|
||||
extr[num_extr++] = server_extr;
|
||||
}
|
||||
extr[num_extr++] = &client_extr;
|
||||
if (!PrintAnnotationsAndRealTimeSpan(os, span.start_send_real_us(),
|
||||
last_time, extr, num_extr, &span)) {
|
||||
os << " start_send_real_us:not-set";
|
||||
}
|
||||
const Protocol* protocol = FindProtocol(span.protocol());
|
||||
const char* protocol_name = (protocol ? protocol->name : "Unknown");
|
||||
const butil::EndPoint remote_side(butil::int2ip(span.remote_ip()), span.remote_port());
|
||||
butil::EndPoint abs_remote_side = remote_side;
|
||||
if (abs_remote_side.ip == loopback_ip) {
|
||||
abs_remote_side.ip = butil::my_ip();
|
||||
}
|
||||
os << " Requesting " << WebEscape(span.full_method_name()) << '@' << remote_side
|
||||
<< ' ' << protocol_name << ' ' << LOG_ID_STR << '=';
|
||||
if (FLAGS_rpcz_hex_log_id) {
|
||||
os << Hex(span.log_id());
|
||||
} else {
|
||||
os << span.log_id();
|
||||
}
|
||||
os << " call_id=" << span.base_cid()
|
||||
<< ' ' << TRACE_ID_STR << '=' << Hex(span.trace_id())
|
||||
<< ' ' << SPAN_ID_STR << '=';
|
||||
if (use_html) {
|
||||
os << "<a href=\"http://" << abs_remote_side
|
||||
<< "/rpcz?" << TRACE_ID_STR << '=' << Hex(span.trace_id())
|
||||
<< '&' << SPAN_ID_STR << '=' << Hex(span.span_id()) << "\">";
|
||||
}
|
||||
os << Hex(span.span_id());
|
||||
if (use_html) {
|
||||
os << "</a>";
|
||||
}
|
||||
os << std::endl;
|
||||
|
||||
if (PrintAnnotationsAndRealTimeSpan(os, span.sent_real_us(),
|
||||
last_time, extr, num_extr, &span)) {
|
||||
os << " [Client SPAN#" << Hex(span.span_id()) << "] Requested(" << span.request_size() << ") [1]" << std::endl;
|
||||
}
|
||||
if (PrintAnnotationsAndRealTimeSpan(os, span.received_real_us(),
|
||||
last_time, extr, num_extr, &span)) {
|
||||
os << " [Client SPAN#" << Hex(span.span_id()) << "] Received response(" << span.response_size() << ")";
|
||||
if (span.base_cid() != 0 && span.ending_cid() != 0) {
|
||||
int64_t ver = span.ending_cid() - span.base_cid();
|
||||
if (ver >= 1) {
|
||||
os << " of request[" << ver << "]";
|
||||
} else {
|
||||
os << " of invalid version=" << ver;
|
||||
}
|
||||
}
|
||||
os << std::endl;
|
||||
}
|
||||
|
||||
if (PrintAnnotationsAndRealTimeSpan(os, span.start_parse_real_us(),
|
||||
last_time, extr, num_extr, &span)) {
|
||||
os << " [Client SPAN#" << Hex(span.span_id()) << "] Processing the response in a new bthread" << std::endl;
|
||||
}
|
||||
|
||||
if (PrintAnnotationsAndRealTimeSpan(
|
||||
os, span.start_callback_real_us(),
|
||||
last_time, extr, num_extr, &span)) {
|
||||
os << " [Client SPAN#" << Hex(span.span_id()) << "] " << (span.async() ? " Enter user's done" : " Back to user's callsite") << std::endl;
|
||||
}
|
||||
|
||||
PrintAnnotations(os, std::numeric_limits<int64_t>::max(),
|
||||
last_time, extr, num_extr, &span);
|
||||
}
|
||||
|
||||
static void PrintClientSpan(std::ostream& os,const RpczSpan& span,
|
||||
bool use_html) {
|
||||
int64_t last_time = span.start_send_real_us();
|
||||
PrintClientSpan(os, span, &last_time, NULL, use_html);
|
||||
}
|
||||
|
||||
static void PrintBthreadSpan(std::ostream& os, const RpczSpan& span, int64_t* last_time,
|
||||
SpanInfoExtractor* server_extr, bool use_html) {
|
||||
SpanInfoExtractor client_extr(span.info().c_str());
|
||||
int num_extr = 0;
|
||||
SpanInfoExtractor* extr[2];
|
||||
if (server_extr) {
|
||||
extr[num_extr++] = server_extr;
|
||||
}
|
||||
extr[num_extr++] = &client_extr;
|
||||
|
||||
// Print span id for bthread span context identification
|
||||
os << " [Bthread SPAN#" << Hex(span.span_id());
|
||||
if (span.parent_span_id() != 0) {
|
||||
os << " parent#" << Hex(span.parent_span_id());
|
||||
}
|
||||
os << "] ";
|
||||
|
||||
PrintAnnotations(os, std::numeric_limits<int64_t>::max(), last_time, extr, num_extr, &span);
|
||||
}
|
||||
|
||||
static void PrintServerSpan(std::ostream& os, const RpczSpan& span,
|
||||
bool use_html) {
|
||||
SpanInfoExtractor server_extr(span.info().c_str());
|
||||
SpanInfoExtractor* extr[1] = { &server_extr };
|
||||
int64_t last_time = span.received_real_us();
|
||||
const butil::EndPoint remote_side(
|
||||
butil::int2ip(span.remote_ip()), span.remote_port());
|
||||
PrintRealDateTime(os, last_time);
|
||||
const Protocol* protocol = FindProtocol(span.protocol());
|
||||
const char* protocol_name = (protocol ? protocol->name : "Unknown");
|
||||
os << " Received request(" << span.request_size() << ") from "
|
||||
<< remote_side << ' ' << protocol_name << ' ' << LOG_ID_STR << '=';
|
||||
if (FLAGS_rpcz_hex_log_id) {
|
||||
os << Hex(span.log_id());
|
||||
} else {
|
||||
os << span.log_id();
|
||||
}
|
||||
// TODO: We can't hyperlink parent_span now because there's no generic
|
||||
// way to get the port of upstream server yet.
|
||||
os << ' ' << TRACE_ID_STR << '=' << Hex(span.trace_id())
|
||||
<< ' ' << SPAN_ID_STR << '=' << Hex(span.span_id());
|
||||
if (span.parent_span_id() != 0) {
|
||||
os << " parent_span=" << Hex(span.parent_span_id());
|
||||
}
|
||||
os << std::endl;
|
||||
if (PrintAnnotationsAndRealTimeSpan(
|
||||
os, span.start_parse_real_us(),
|
||||
&last_time, extr, ARRAY_SIZE(extr), &span)) {
|
||||
os << " [Server SPAN#" << Hex(span.span_id()) << "] Processing the request in a new bthread" << std::endl;
|
||||
}
|
||||
|
||||
bool entered_user_method = false;
|
||||
if (PrintAnnotationsAndRealTimeSpan(
|
||||
os, span.start_callback_real_us(),
|
||||
&last_time, extr, ARRAY_SIZE(extr), &span)) {
|
||||
entered_user_method = true;
|
||||
os << " [Server SPAN#" << Hex(span.span_id()) << "] Enter " << WebEscape(span.full_method_name()) << std::endl;
|
||||
}
|
||||
|
||||
const int nclient = span.client_spans_size();
|
||||
for (int i = 0; i < nclient; ++i) {
|
||||
auto& client_span = span.client_spans(i);
|
||||
if (client_span.type() == SPAN_TYPE_CLIENT) {
|
||||
PrintClientSpan(os, client_span, &last_time, &server_extr, use_html);
|
||||
} else {
|
||||
PrintBthreadSpan(os, client_span, &last_time, &server_extr, use_html);
|
||||
}
|
||||
}
|
||||
|
||||
if (PrintAnnotationsAndRealTimeSpan(
|
||||
os, span.start_send_real_us(),
|
||||
&last_time, extr, ARRAY_SIZE(extr), &span)) {
|
||||
if (entered_user_method) {
|
||||
os << " [Server SPAN#" << Hex(span.span_id()) << "] Leave " << WebEscape(span.full_method_name()) << std::endl;
|
||||
} else {
|
||||
os << " [Server SPAN#" << Hex(span.span_id()) << "] Responding" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (PrintAnnotationsAndRealTimeSpan(
|
||||
os, span.sent_real_us(),
|
||||
&last_time, extr, ARRAY_SIZE(extr), &span)) {
|
||||
os << " [Server SPAN#" << Hex(span.span_id()) << "] Responded(" << span.response_size() << ')' << std::endl;
|
||||
}
|
||||
|
||||
PrintAnnotations(os, std::numeric_limits<int64_t>::max(),
|
||||
&last_time, extr, ARRAY_SIZE(extr), &span);
|
||||
}
|
||||
|
||||
class RpczSpanFilter : public SpanFilter {
|
||||
public:
|
||||
RpczSpanFilter()
|
||||
: _min_latency(std::numeric_limits<int64_t>::min())
|
||||
, _min_request_size(std::numeric_limits<int>::min())
|
||||
, _min_response_size(std::numeric_limits<int>::min())
|
||||
, _log_id(0)
|
||||
, _check_log_id(false)
|
||||
, _check_error_code(false)
|
||||
, _error_code(0)
|
||||
{}
|
||||
|
||||
void CheckLatency(int64_t min_latency) { _min_latency = min_latency; }
|
||||
|
||||
void CheckRequest(int64_t min_request_size) {
|
||||
_min_request_size = min_request_size;
|
||||
}
|
||||
|
||||
void CheckResponse(int64_t min_response_size) {
|
||||
_min_response_size = min_response_size;
|
||||
}
|
||||
|
||||
void CheckLogId(uint64_t log_id) {
|
||||
_check_log_id = true;
|
||||
_log_id = log_id;
|
||||
}
|
||||
|
||||
void CheckErrorCode(int error_code) {
|
||||
_check_error_code = true;
|
||||
_error_code = error_code;
|
||||
}
|
||||
|
||||
bool Keep(const BriefSpan& span) {
|
||||
return span.latency_us() >= _min_latency &&
|
||||
span.request_size() >= _min_request_size &&
|
||||
span.response_size() >= _min_response_size &&
|
||||
(!_check_log_id || span.log_id() == _log_id) &&
|
||||
(!_check_error_code || span.error_code() == _error_code);
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t _min_latency;
|
||||
int _min_request_size;
|
||||
int _min_response_size;
|
||||
uint64_t _log_id;
|
||||
bool _check_log_id;
|
||||
bool _check_error_code;
|
||||
int _error_code;
|
||||
};
|
||||
|
||||
static int64_t ParseDateTime(const std::string& time_str) {
|
||||
struct tm timeinfo;
|
||||
int64_t microseconds = 999999;
|
||||
char* endptr = strptime(time_str.c_str(), "%Y/%m/%d-%H:%M:%S", &timeinfo);
|
||||
if (endptr == NULL) {
|
||||
time_t now;
|
||||
time(&now);
|
||||
if (localtime_r(&now, &timeinfo) == NULL) {
|
||||
return -1;
|
||||
}
|
||||
endptr = strptime(time_str.c_str(), "%H:%M:%S", &timeinfo);
|
||||
if (endptr == NULL) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (*endptr == '.') {
|
||||
char* endptr2;
|
||||
microseconds = strtol(endptr + 1, &endptr2, 10);
|
||||
if (*endptr2 != '\0') {
|
||||
microseconds = 999999;
|
||||
}
|
||||
}
|
||||
return timelocal(&timeinfo) * 1000000L + microseconds;
|
||||
}
|
||||
|
||||
static bool ParseUint64(const std::string* str, uint64_t* val) {
|
||||
if (NULL == str) {
|
||||
return false;
|
||||
}
|
||||
const char* p = str->c_str();
|
||||
char* endptr = NULL;
|
||||
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
|
||||
*val = strtoull(p + 2, &endptr, 16);
|
||||
return (*endptr == '\0');
|
||||
}
|
||||
*val = strtoull(p, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
return true;
|
||||
}
|
||||
if ((*endptr >= 'a' && *endptr <= 'f') ||
|
||||
(*endptr >= 'A' && *endptr <= 'F')) {
|
||||
*val = strtoull(p, &endptr, 16);
|
||||
return (*endptr == '\0');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RpczService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest*,
|
||||
::brpc::RpczResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
uint64_t trace_id = 0;
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
|
||||
butil::IOBufBuilder os;
|
||||
if (use_html) {
|
||||
os << "<!DOCTYPE html><html><head>\n"
|
||||
<< "<script language=\"javascript\" type=\"text/javascript\" src=\"/js/jquery_min\"></script>\n"
|
||||
<< TabsHead()
|
||||
<< "</head><body>";
|
||||
cntl->server()->PrintTabsBody(os, "rpcz");
|
||||
}
|
||||
|
||||
if (!FLAGS_enable_rpcz && !has_span_db()) {
|
||||
if (use_html) {
|
||||
os << "<input type='button' "
|
||||
"onclick='location.href=\"/rpcz/enable\";' value='enable' />"
|
||||
" rpcz to track recent RPC calls with small overhead, "
|
||||
"you can turn it off at any time.";
|
||||
} else {
|
||||
os << "rpcz is not enabled yet. You can turn on/off rpcz by accessing "
|
||||
"/rpcz/enable and /rpcz/disable respectively.";
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
return;
|
||||
}
|
||||
butil::EndPoint my_addr(butil::my_ip(),
|
||||
cntl->server()->listen_address().port);
|
||||
|
||||
const std::string* trace_id_str =
|
||||
cntl->http_request().uri().GetQuery(TRACE_ID_STR);
|
||||
if (trace_id_str) {
|
||||
char* endptr;
|
||||
trace_id = strtoull(trace_id_str->c_str(), &endptr, 16);
|
||||
if (*endptr != '\0') {
|
||||
trace_id = 0;
|
||||
}
|
||||
}
|
||||
if (trace_id != 0) { // Point search
|
||||
uint64_t span_id = 0;
|
||||
const std::string* span_id_str =
|
||||
cntl->http_request().uri().GetQuery(SPAN_ID_STR);
|
||||
if (span_id_str) {
|
||||
char* endptr;
|
||||
span_id = strtoull(span_id_str->c_str(), &endptr, 16);
|
||||
if (*endptr != '\0') {
|
||||
span_id = 0;
|
||||
}
|
||||
}
|
||||
std::deque<RpczSpan> spans;
|
||||
if (span_id == 0) {
|
||||
FindSpans(trace_id, &spans);
|
||||
std::sort(spans.begin(), spans.end(), CompareByStartRealTime());
|
||||
} else {
|
||||
spans.resize(1);
|
||||
if (FindSpan(trace_id, span_id, &spans[0]) != 0) {
|
||||
spans.clear();
|
||||
}
|
||||
}
|
||||
if (spans.empty()) {
|
||||
os << "Fail to find any spans"
|
||||
<< (use_html ? "</body></html>" : "");
|
||||
os.move_to(cntl->response_attachment());
|
||||
return;
|
||||
}
|
||||
if (use_html) {
|
||||
os << "<pre>\n";
|
||||
}
|
||||
for (size_t i = 0; i < spans.size(); ++i) {
|
||||
RpczSpan& span = spans[i];
|
||||
if (span.type() == SPAN_TYPE_SERVER) {
|
||||
PrintServerSpan(os, span, use_html);
|
||||
} else {
|
||||
PrintClientSpan(os, span, use_html);
|
||||
}
|
||||
os << std::endl;
|
||||
}
|
||||
if (use_html) {
|
||||
os << "</pre></body></html>";
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
return;
|
||||
} else {
|
||||
const std::string* time_str =
|
||||
cntl->http_request().uri().GetQuery(TIME_STR);
|
||||
int64_t start_tm;
|
||||
if (time_str == NULL) {
|
||||
start_tm = butil::gettimeofday_us();
|
||||
} else {
|
||||
start_tm = ParseDateTime(*time_str);
|
||||
if (start_tm < 0) {
|
||||
os << "Invalid " << TIME_STR << "=`" << time_str << '\''
|
||||
<< (use_html ? "</body></html>" : "");
|
||||
os.move_to(cntl->response_attachment());
|
||||
return;
|
||||
}
|
||||
}
|
||||
int max_count = 100;
|
||||
const std::string* max_scan_str =
|
||||
cntl->http_request().uri().GetQuery(MAX_SCAN_STR);
|
||||
if (max_scan_str) {
|
||||
char* endptr;
|
||||
int max_count2 = strtol(max_scan_str->c_str(), &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
max_count = max_count2;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up SpanFilter.
|
||||
RpczSpanFilter filter;
|
||||
const std::string* min_latency_str =
|
||||
cntl->http_request().uri().GetQuery(MIN_LATENCY_STR);
|
||||
if (min_latency_str) {
|
||||
char* endptr;
|
||||
filter.CheckLatency(strtoll(min_latency_str->c_str(), &endptr, 10));
|
||||
}
|
||||
const std::string* min_reqsize_str =
|
||||
cntl->http_request().uri().GetQuery(MIN_REQUEST_SIZE_STR);
|
||||
if (min_reqsize_str) {
|
||||
char* endptr;
|
||||
filter.CheckRequest(strtol(min_reqsize_str->c_str(), &endptr, 10));
|
||||
}
|
||||
const std::string* min_respsize_str =
|
||||
cntl->http_request().uri().GetQuery(MIN_RESPONSE_SIZE_STR);
|
||||
if (min_respsize_str) {
|
||||
char* endptr;
|
||||
filter.CheckResponse(strtol(min_respsize_str->c_str(), &endptr, 10));
|
||||
}
|
||||
uint64_t log_id = 0;
|
||||
if (ParseUint64(cntl->http_request().uri().GetQuery(LOG_ID_STR),
|
||||
&log_id)) {
|
||||
filter.CheckLogId(log_id);
|
||||
}
|
||||
const std::string* error_code_str =
|
||||
cntl->http_request().uri().GetQuery(ERROR_CODE_STR);
|
||||
if (error_code_str) {
|
||||
char* endptr;
|
||||
filter.CheckErrorCode(strtol(error_code_str->c_str(), &endptr, 10));
|
||||
}
|
||||
|
||||
max_count = std::max(std::min(max_count, 10000), 1);
|
||||
std::deque<BriefSpan> spans;
|
||||
ListSpans(start_tm, max_count, &spans, &filter);
|
||||
if (spans.empty()) {
|
||||
os << "Fail to find matched spans"
|
||||
<< (use_html ? "</body></html>" : "");
|
||||
os.move_to(cntl->response_attachment());
|
||||
return;
|
||||
}
|
||||
if (use_html) {
|
||||
const char* action = (FLAGS_enable_rpcz ? "disable" : "enable");
|
||||
os << "<div><input type='button' onclick='location.href=\"/rpcz/"
|
||||
<< action << "\";' value='" << action << "' /></div>" "<pre>\n";
|
||||
}
|
||||
for (size_t i = 0; i < spans.size(); ++i) {
|
||||
BriefSpan& span = spans[i];
|
||||
int64_t last_time = span.start_real_us();
|
||||
PrintRealDateTime(os, last_time);
|
||||
PrintElapse(os, span.latency_us() + last_time, &last_time);
|
||||
os << ' ' << (span.type() == SPAN_TYPE_SERVER ? 'S' : 'C')
|
||||
<< ' ' << TRACE_ID_STR << '=';
|
||||
if (use_html) {
|
||||
os << "<a href=\"/rpcz?" << TRACE_ID_STR
|
||||
<< '=' << Hex(span.trace_id()) << "\">";
|
||||
}
|
||||
os << Hex(span.trace_id());
|
||||
if (use_html) {
|
||||
os << "</a>";
|
||||
}
|
||||
|
||||
os << ' ' << SPAN_ID_STR << '=';
|
||||
if (use_html) {
|
||||
os << "<a href=\"/rpcz?" << TRACE_ID_STR << '='
|
||||
<< Hex(span.trace_id())
|
||||
<< '&' << SPAN_ID_STR << '=' << Hex(span.span_id()) << "\">";
|
||||
}
|
||||
os << Hex(span.span_id());
|
||||
if (use_html) {
|
||||
os << "</a>";
|
||||
}
|
||||
os << ' ' << LOG_ID_STR << '=';
|
||||
if (FLAGS_rpcz_hex_log_id) {
|
||||
os << Hex(span.log_id());
|
||||
} else {
|
||||
os << span.log_id();
|
||||
}
|
||||
os << ' ' << WebEscape(span.full_method_name()) << '(' << span.request_size()
|
||||
<< ")=" << span.response_size();
|
||||
|
||||
if (span.error_code() == 0) {
|
||||
os << " [OK]";
|
||||
} else {
|
||||
os << " [" << berror(span.error_code()) << "] ";
|
||||
}
|
||||
os << std::endl;
|
||||
}
|
||||
if (use_html) {
|
||||
os << "</pre></body></html>";
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
}
|
||||
}
|
||||
|
||||
void RpczService::GetTabInfo(TabInfoList* info_list) const {
|
||||
TabInfo* info = info_list->add();
|
||||
info->path = "/rpcz";
|
||||
info->tab_name = "rpcz";
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,66 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_RPCZ_SERVICE_H
|
||||
#define BRPC_RPCZ_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
#include "brpc/builtin/tabbed.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class RpczService : public rpcz, public Tabbed {
|
||||
public:
|
||||
void enable(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest* request,
|
||||
::brpc::RpczResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void disable(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest* request,
|
||||
::brpc::RpczResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void stats(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest* request,
|
||||
::brpc::RpczResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void hex_log_id(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest* request,
|
||||
::brpc::RpczResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void dec_log_id(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest* request,
|
||||
::brpc::RpczResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::RpczRequest* request,
|
||||
::brpc::RpczResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void GetTabInfo(brpc::TabInfoList*) const;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_RPCZ_SERVICE_H
|
||||
@@ -0,0 +1,55 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <ostream>
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/builtin/sockets_service.h"
|
||||
#include "brpc/socket.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
void SocketsService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::SocketsRequest*,
|
||||
::brpc::SocketsResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
butil::IOBufBuilder os;
|
||||
const std::string& constraint = cntl->http_request().unresolved_path();
|
||||
|
||||
if (constraint.empty()) {
|
||||
os << "# Use /sockets/<SocketId>\n"
|
||||
<< butil::describe_resources<Socket>() << '\n';
|
||||
} else {
|
||||
char* endptr = NULL;
|
||||
SocketId sid = strtoull(constraint.c_str(), &endptr, 10);
|
||||
if (*endptr == '\0' || *endptr == '/') {
|
||||
Socket::DebugSocket(os, sid);
|
||||
} else {
|
||||
cntl->SetFailed(ENOMETHOD, "path=%s is not a SocketId",
|
||||
constraint.c_str());
|
||||
}
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,38 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_SOCKETS_SERVICE_H
|
||||
#define BRPC_SOCKETS_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class SocketsService : public sockets {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::SocketsRequest* request,
|
||||
::brpc::SocketsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_SOCKETS_SERVICE_H
|
||||
@@ -0,0 +1,529 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <pthread.h>
|
||||
#include "brpc/builtin/sorttable_js.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
static pthread_once_t s_sorttable_buf_once = PTHREAD_ONCE_INIT;
|
||||
static butil::IOBuf* s_sorttable_buf = NULL;
|
||||
static void InitSortTableBuf() {
|
||||
s_sorttable_buf = new butil::IOBuf;
|
||||
s_sorttable_buf->append(sorttable_js());
|
||||
}
|
||||
const butil::IOBuf& sorttable_js_iobuf() {
|
||||
pthread_once(&s_sorttable_buf_once, InitSortTableBuf);
|
||||
return *s_sorttable_buf;
|
||||
}
|
||||
|
||||
/*
|
||||
SortTable
|
||||
version 2
|
||||
7th April 2007
|
||||
Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
|
||||
|
||||
Instructions:
|
||||
Download this file
|
||||
Add <script src=\"sorttable.js\"></script> to your HTML
|
||||
Add class=\"sortable\" to any table you'd like to make sortable
|
||||
Click on the headers to sort
|
||||
|
||||
Thanks to many, many people for contributions and suggestions.
|
||||
Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
|
||||
This basically means: do what you want with it.
|
||||
*/
|
||||
|
||||
const char* sorttable_js() {
|
||||
return "var stIsIE = /*@cc_on!@*/false;\n"
|
||||
"sorttable = {\n"
|
||||
" init: function() {\n"
|
||||
" // quit if this function has already been called\n"
|
||||
" if (arguments.callee.done) return;\n"
|
||||
" // flag this function so we don't do the same thing twice\n"
|
||||
" arguments.callee.done = true;\n"
|
||||
" // kill the timer\n"
|
||||
" if (_timer) clearInterval(_timer);\n"
|
||||
"\n"
|
||||
" if (!document.createElement || !document.getElementsByTagName) return;\n"
|
||||
"\n"
|
||||
" sorttable.DATE_RE = /^(\\d\\d?)[\\/\\.-](\\d\\d?)[\\/\\.-]((\\d\\d)?\\d\\d)$/;\n"
|
||||
"\n"
|
||||
" forEach(document.getElementsByTagName('table'), function(table) {\n"
|
||||
" if (table.className.search(/\\bsortable\\b/) != -1) {\n"
|
||||
" sorttable.makeSortable(table);\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" },\n"
|
||||
"\n"
|
||||
" makeSortable: function(table) {\n"
|
||||
" if (table.getElementsByTagName('thead').length == 0) {\n"
|
||||
" // table doesn't have a tHead. Since it should have, create one and\n"
|
||||
" // put the first table row in it.\n"
|
||||
" the = document.createElement('thead');\n"
|
||||
" the.appendChild(table.rows[0]);\n"
|
||||
" table.insertBefore(the,table.firstChild);\n"
|
||||
" }\n"
|
||||
" // Safari doesn't support table.tHead, sigh\n"
|
||||
" if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];\n"
|
||||
"\n"
|
||||
" if (table.tHead.rows.length != 1) return; // can't cope with two header rows\n"
|
||||
"\n"
|
||||
" // Sorttable v1 put rows with a class of \"sortbottom\" at the bottom (as\n"
|
||||
" // \"total\" rows, for example). This is B&R, since what you're supposed\n"
|
||||
" // to do is put them in a tfoot. So, if there are sortbottom rows,\n"
|
||||
" // for backwards compatibility, move them to tfoot (creating it if needed).\n"
|
||||
" sortbottomrows = [];\n"
|
||||
" for (var i=0; i<table.rows.length; i++) {\n"
|
||||
" if (table.rows[i].className.search(/\\bsortbottom\\b/) != -1) {\n"
|
||||
" sortbottomrows[sortbottomrows.length] = table.rows[i];\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (sortbottomrows) {\n"
|
||||
" if (table.tFoot == null) {\n"
|
||||
" // table doesn't have a tfoot. Create one.\n"
|
||||
" tfo = document.createElement('tfoot');\n"
|
||||
" table.appendChild(tfo);\n"
|
||||
" }\n"
|
||||
" for (var i=0; i<sortbottomrows.length; i++) {\n"
|
||||
" tfo.appendChild(sortbottomrows[i]);\n"
|
||||
" }\n"
|
||||
" delete sortbottomrows;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" // work through each column and calculate its type\n"
|
||||
" headrow = table.tHead.rows[0].cells;\n"
|
||||
" for (var i=0; i<headrow.length; i++) {\n"
|
||||
" // manually override the type with a sorttable_type attribute\n"
|
||||
" if (!headrow[i].className.match(/\\bsorttable_nosort\\b/)) { // skip this col\n"
|
||||
" mtch = headrow[i].className.match(/\\bsorttable_([a-z0-9]+)\\b/);\n"
|
||||
" if (mtch) { override = mtch[1]; }\n"
|
||||
" if (mtch && typeof sorttable[\"sort_\"+override] == 'function') {\n"
|
||||
" headrow[i].sorttable_sortfunction = sorttable[\"sort_\"+override];\n"
|
||||
" } else {\n"
|
||||
" headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);\n"
|
||||
" }\n"
|
||||
" // make it clickable to sort\n"
|
||||
" headrow[i].sorttable_columnindex = i;\n"
|
||||
" headrow[i].sorttable_tbody = table.tBodies[0];\n"
|
||||
" dean_addEvent(headrow[i],\"click\", sorttable.innerSortFunction = function(e) {\n"
|
||||
"\n"
|
||||
" if (this.className.search(/\\bsorttable_sorted\\b/) != -1) {\n"
|
||||
" // if we're already sorted by this column, just\n"
|
||||
" // reverse the table, which is quicker\n"
|
||||
" sorttable.reverse(this.sorttable_tbody);\n"
|
||||
" this.className = this.className.replace('sorttable_sorted',\n"
|
||||
" 'sorttable_sorted_reverse');\n"
|
||||
" this.removeChild(document.getElementById('sorttable_sortfwdind'));\n"
|
||||
" sortrevind = document.createElement('span');\n"
|
||||
" sortrevind.id = \"sorttable_sortrevind\";\n"
|
||||
" sortrevind.innerHTML = stIsIE ? ' <font face=\"webdings\">5</font>' :\n"
|
||||
"' ▴';\n"
|
||||
" this.appendChild(sortrevind);\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" if (this.className.search(/\\bsorttable_sorted_reverse\\b/) != -1) {\n"
|
||||
" // if we're already sorted by this column in reverse, just\n"
|
||||
" // re-reverse the table, which is quicker\n"
|
||||
" sorttable.reverse(this.sorttable_tbody);\n"
|
||||
" this.className = this.className.replace('sorttable_sorted_reverse',\n"
|
||||
" 'sorttable_sorted');\n"
|
||||
" this.removeChild(document.getElementById('sorttable_sortrevind'));\n"
|
||||
" sortfwdind = document.createElement('span');\n"
|
||||
" sortfwdind.id = \"sorttable_sortfwdind\";\n"
|
||||
" sortfwdind.innerHTML = stIsIE ? ' <font face=\"webdings\">6</font>' :\n"
|
||||
"' ▾';\n"
|
||||
" this.appendChild(sortfwdind);\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" // remove sorttable_sorted classes\n"
|
||||
" theadrow = this.parentNode;\n"
|
||||
" forEach(theadrow.childNodes, function(cell) {\n"
|
||||
" if (cell.nodeType == 1) { // an element\n"
|
||||
" cell.className = cell.className.replace('sorttable_sorted_reverse','');\n"
|
||||
" cell.className = cell.className.replace('sorttable_sorted','');\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
" sortfwdind = document.getElementById('sorttable_sortfwdind');\n"
|
||||
" if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }\n"
|
||||
" sortrevind = document.getElementById('sorttable_sortrevind');\n"
|
||||
" if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }\n"
|
||||
"\n"
|
||||
" this.className += ' sorttable_sorted';\n"
|
||||
" sortfwdind = document.createElement('span');\n"
|
||||
" sortfwdind.id = \"sorttable_sortfwdind\";\n"
|
||||
" sortfwdind.innerHTML = stIsIE ? ' <font face=\"webdings\">6</font>' : ' ▾';\n"
|
||||
" this.appendChild(sortfwdind);\n"
|
||||
"\n"
|
||||
" // build an array to sort. This is a Schwartzian transform thing,\n"
|
||||
" // i.e., we \"decorate\" each row with the actual sort key,\n"
|
||||
" // sort based on the sort keys, and then put the rows back in order\n"
|
||||
" // which is a lot faster because you only do getInnerText once per row\n"
|
||||
" row_array = [];\n"
|
||||
" col = this.sorttable_columnindex;\n"
|
||||
" rows = this.sorttable_tbody.rows;\n"
|
||||
" for (var j=0; j<rows.length; j++) {\n"
|
||||
" row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]];\n"
|
||||
" }\n"
|
||||
" //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);\n"
|
||||
" row_array.sort(this.sorttable_sortfunction);\n"
|
||||
"\n"
|
||||
" tb = this.sorttable_tbody;\n"
|
||||
" for (var j=0; j<row_array.length; j++) {\n"
|
||||
" tb.appendChild(row_array[j][1]);\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" delete row_array;\n"
|
||||
" });\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" },\n"
|
||||
"\n"
|
||||
" guessType: function(table, column) {\n"
|
||||
" // guess the type of a column based on its first non-blank row\n"
|
||||
" sortfn = sorttable.sort_alpha;\n"
|
||||
" for (var i=0; i<table.tBodies[0].rows.length; i++) {\n"
|
||||
" text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);\n"
|
||||
" if (text != '') {\n"
|
||||
" if (text.match(/^-?[$]?[\\d,.]+%?$/)) {\n"
|
||||
" return sorttable.sort_numeric;\n"
|
||||
" }\n"
|
||||
" // check for a date: dd/mm/yyyy or dd/mm/yy\n"
|
||||
" // can have / or . or - as separator\n"
|
||||
" // can be mm/dd as well\n"
|
||||
" possdate = text.match(sorttable.DATE_RE)\n"
|
||||
" if (possdate) {\n"
|
||||
" // looks like a date\n"
|
||||
" first = parseInt(possdate[1]);\n"
|
||||
" second = parseInt(possdate[2]);\n"
|
||||
" if (first > 12) {\n"
|
||||
" // definitely dd/mm\n"
|
||||
" return sorttable.sort_ddmm;\n"
|
||||
" } else if (second > 12) {\n"
|
||||
" return sorttable.sort_mmdd;\n"
|
||||
" } else {\n"
|
||||
" // looks like a date, but we can't tell which, so assume\n"
|
||||
" // that it's dd/mm (English imperialism!) and keep looking\n"
|
||||
" sortfn = sorttable.sort_ddmm;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return sortfn;\n"
|
||||
" },\n"
|
||||
"\n"
|
||||
" getInnerText: function(node) {\n"
|
||||
" // gets the text we want to use for sorting for a cell.\n"
|
||||
" // strips leading and trailing whitespace.\n"
|
||||
" // this is *not* a generic getInnerText function; it's special to sorttable.\n"
|
||||
" // for example, you can override the cell text with a customkey attribute.\n"
|
||||
" // it also gets .value for <input> fields.\n"
|
||||
"\n"
|
||||
" if (!node) return \"\";\n"
|
||||
"\n"
|
||||
" hasInputs = (typeof node.getElementsByTagName == 'function') &&\n"
|
||||
" node.getElementsByTagName('input').length;\n"
|
||||
"\n"
|
||||
" if (node.getAttribute(\"sorttable_customkey\") != null) {\n"
|
||||
" return node.getAttribute(\"sorttable_customkey\");\n"
|
||||
" }\n"
|
||||
" else if (typeof node.textContent != 'undefined' && !hasInputs) {\n"
|
||||
" return node.textContent.replace(/^\\s+|\\s+$/g, '');\n"
|
||||
" }\n"
|
||||
" else if (typeof node.innerText != 'undefined' && !hasInputs) {\n"
|
||||
" return node.innerText.replace(/^\\s+|\\s+$/g, '');\n"
|
||||
" }\n"
|
||||
" else if (typeof node.text != 'undefined' && !hasInputs) {\n"
|
||||
" return node.text.replace(/^\\s+|\\s+$/g, '');\n"
|
||||
" }\n"
|
||||
" else {\n"
|
||||
" switch (node.nodeType) {\n"
|
||||
" case 3:\n"
|
||||
" if (node.nodeName.toLowerCase() == 'input') {\n"
|
||||
" return node.value.replace(/^\\s+|\\s+$/g, '');\n"
|
||||
" }\n"
|
||||
" case 4:\n"
|
||||
" return node.nodeValue.replace(/^\\s+|\\s+$/g, '');\n"
|
||||
" break;\n"
|
||||
" case 1:\n"
|
||||
" case 11:\n"
|
||||
" var innerText = '';\n"
|
||||
" for (var i = 0; i < node.childNodes.length; i++) {\n"
|
||||
" innerText += sorttable.getInnerText(node.childNodes[i]);\n"
|
||||
" }\n"
|
||||
" return innerText.replace(/^\\s+|\\s+$/g, '');\n"
|
||||
" break;\n"
|
||||
" default:\n"
|
||||
" return '';\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" },\n"
|
||||
"\n"
|
||||
" reverse: function(tbody) {\n"
|
||||
" // reverse the rows in a tbody\n"
|
||||
" newrows = [];\n"
|
||||
" for (var i=0; i<tbody.rows.length; i++) {\n"
|
||||
" newrows[newrows.length] = tbody.rows[i];\n"
|
||||
" }\n"
|
||||
" for (var i=newrows.length-1; i>=0; i--) {\n"
|
||||
" tbody.appendChild(newrows[i]);\n"
|
||||
" }\n"
|
||||
" delete newrows;\n"
|
||||
" },\n"
|
||||
"\n"
|
||||
" sort_numeric: function(a,b) {\n"
|
||||
" aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));\n"
|
||||
" if (isNaN(aa)) aa = 0;\n"
|
||||
" bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));\n"
|
||||
" if (isNaN(bb)) bb = 0;\n"
|
||||
" return aa-bb;\n"
|
||||
" },\n"
|
||||
" sort_alpha: function(a,b) {\n"
|
||||
" if (a[0]==b[0]) return 0;\n"
|
||||
" if (a[0]<b[0]) return -1;\n"
|
||||
" return 1;\n"
|
||||
" },\n"
|
||||
" sort_ddmm: function(a,b) {\n"
|
||||
" mtch = a[0].match(sorttable.DATE_RE);\n"
|
||||
" y = mtch[3]; m = mtch[2]; d = mtch[1];\n"
|
||||
" if (m.length == 1) m = '0'+m;\n"
|
||||
" if (d.length == 1) d = '0'+d;\n"
|
||||
" dt1 = y+m+d;\n"
|
||||
" mtch = b[0].match(sorttable.DATE_RE);\n"
|
||||
" y = mtch[3]; m = mtch[2]; d = mtch[1];\n"
|
||||
" if (m.length == 1) m = '0'+m;\n"
|
||||
" if (d.length == 1) d = '0'+d;\n"
|
||||
" dt2 = y+m+d;\n"
|
||||
" if (dt1==dt2) return 0;\n"
|
||||
" if (dt1<dt2) return -1;\n"
|
||||
" return 1;\n"
|
||||
" },\n"
|
||||
" sort_mmdd: function(a,b) {\n"
|
||||
" mtch = a[0].match(sorttable.DATE_RE);\n"
|
||||
" y = mtch[3]; d = mtch[2]; m = mtch[1];\n"
|
||||
" if (m.length == 1) m = '0'+m;\n"
|
||||
" if (d.length == 1) d = '0'+d;\n"
|
||||
" dt1 = y+m+d;\n"
|
||||
" mtch = b[0].match(sorttable.DATE_RE);\n"
|
||||
" y = mtch[3]; d = mtch[2]; m = mtch[1];\n"
|
||||
" if (m.length == 1) m = '0'+m;\n"
|
||||
" if (d.length == 1) d = '0'+d;\n"
|
||||
" dt2 = y+m+d;\n"
|
||||
" if (dt1==dt2) return 0;\n"
|
||||
" if (dt1<dt2) return -1;\n"
|
||||
" return 1;\n"
|
||||
" },\n"
|
||||
"\n"
|
||||
" shaker_sort: function(list, comp_func) {\n"
|
||||
" // A stable sort function to allow multi-level sorting of data\n"
|
||||
" // see: http://en.wikipedia.org/wiki/Cocktail_sort\n"
|
||||
" // thanks to Joseph Nahmias\n"
|
||||
" var b = 0;\n"
|
||||
" var t = list.length - 1;\n"
|
||||
" var swap = true;\n"
|
||||
"\n"
|
||||
" while(swap) {\n"
|
||||
" swap = false;\n"
|
||||
" for(var i = b; i < t; ++i) {\n"
|
||||
" if ( comp_func(list[i], list[i+1]) > 0 ) {\n"
|
||||
" var q = list[i]; list[i] = list[i+1]; list[i+1] = q;\n"
|
||||
" swap = true;\n"
|
||||
" }\n"
|
||||
" } // for\n"
|
||||
" t--;\n"
|
||||
"\n"
|
||||
" if (!swap) break;\n"
|
||||
"\n"
|
||||
" for(var i = t; i > b; --i) {\n"
|
||||
" if ( comp_func(list[i], list[i-1]) < 0 ) {\n"
|
||||
" var q = list[i]; list[i] = list[i-1]; list[i-1] = q;\n"
|
||||
" swap = true;\n"
|
||||
" }\n"
|
||||
" } // for\n"
|
||||
" b++;\n"
|
||||
"\n"
|
||||
" } // while(swap)\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"/* ******************************************************************\n"
|
||||
" Supporting functions: bundled here to avoid depending on a library\n"
|
||||
" ****************************************************************** */\n"
|
||||
"\n"
|
||||
"// Dean Edwards/Matthias Miller/John Resig\n"
|
||||
"\n"
|
||||
"/* for Mozilla/Opera9 */\n"
|
||||
"if (document.addEventListener) {\n"
|
||||
" document.addEventListener(\"DOMContentLoaded\", sorttable.init, false);\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"/* for Internet Explorer */\n"
|
||||
"/*@cc_on @*/\n"
|
||||
"/*@if (@_win32)\n"
|
||||
" document.write(\"<script id=__ie_onload defer src=javascript:void(0)><\\/script>\");\n"
|
||||
" var script = document.getElementById(\"__ie_onload\");\n"
|
||||
" script.onreadystatechange = function() {\n"
|
||||
" if (this.readyState == \"complete\") {\n"
|
||||
" sorttable.init(); // call the onload handler\n"
|
||||
" }\n"
|
||||
" };\n"
|
||||
"/*@end @*/\n"
|
||||
"\n"
|
||||
"/* for Safari */\n"
|
||||
"if (/WebKit/i.test(navigator.userAgent)) { // sniff\n"
|
||||
" var _timer = setInterval(function() {\n"
|
||||
" if (/loaded|complete/.test(document.readyState)) {\n"
|
||||
" sorttable.init(); // call the onload handler\n"
|
||||
" }\n"
|
||||
" }, 10);\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"/* for other browsers */\n"
|
||||
"window.onload = sorttable.init;\n"
|
||||
"\n"
|
||||
"// written by Dean Edwards, 2005\n"
|
||||
"// with input from Tino Zijdel, Matthias Miller, Diego Perini\n"
|
||||
"\n"
|
||||
"// http://dean.edwards.name/weblog/2005/10/add-event/\n"
|
||||
"\n"
|
||||
"function dean_addEvent(element, type, handler) {\n"
|
||||
" if (element.addEventListener) {\n"
|
||||
" element.addEventListener(type, handler, false);\n"
|
||||
" } else {\n"
|
||||
" // assign each event handler a unique ID\n"
|
||||
" if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++;\n"
|
||||
" // create a hash table of event types for the element\n"
|
||||
" if (!element.events) element.events = {};\n"
|
||||
" // create a hash table of event handlers for each element/event pair\n"
|
||||
" var handlers = element.events[type];\n"
|
||||
" if (!handlers) {\n"
|
||||
" handlers = element.events[type] = {};\n"
|
||||
" // store the existing event handler (if there is one)\n"
|
||||
" if (element[\"on\" + type]) {\n"
|
||||
" handlers[0] = element[\"on\" + type];\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" // store the event handler in the hash table\n"
|
||||
" handlers[handler.$$guid] = handler;\n"
|
||||
" // assign a global event handler to do all the work\n"
|
||||
" element[\"on\" + type] = handleEvent;\n"
|
||||
" }\n"
|
||||
"};\n"
|
||||
"// a counter used to create unique IDs\n"
|
||||
"dean_addEvent.guid = 1;\n"
|
||||
"\n"
|
||||
"function removeEvent(element, type, handler) {\n"
|
||||
" if (element.removeEventListener) {\n"
|
||||
" element.removeEventListener(type, handler, false);\n"
|
||||
" } else {\n"
|
||||
" // delete the event handler from the hash table\n"
|
||||
" if (element.events && element.events[type]) {\n"
|
||||
" delete element.events[type][handler.$$guid];\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"function handleEvent(event) {\n"
|
||||
" var returnValue = true;\n"
|
||||
" // grab the event object (IE uses a global event object)\n"
|
||||
" event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow ||\n"
|
||||
"window).event);\n"
|
||||
" // get a reference to the hash table of event handlers\n"
|
||||
" var handlers = this.events[event.type];\n"
|
||||
" // execute each event handler\n"
|
||||
" for (var i in handlers) {\n"
|
||||
" this.$$handleEvent = handlers[i];\n"
|
||||
" if (this.$$handleEvent(event) === false) {\n"
|
||||
" returnValue = false;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return returnValue;\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"function fixEvent(event) {\n"
|
||||
" // add W3C standard event methods\n"
|
||||
" event.preventDefault = fixEvent.preventDefault;\n"
|
||||
" event.stopPropagation = fixEvent.stopPropagation;\n"
|
||||
" return event;\n"
|
||||
"};\n"
|
||||
"fixEvent.preventDefault = function() {\n"
|
||||
" this.returnValue = false;\n"
|
||||
"};\n"
|
||||
"fixEvent.stopPropagation = function() {\n"
|
||||
" this.cancelBubble = true;\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"// Dean's forEach: http://dean.edwards.name/butil/forEach.js\n"
|
||||
"/*\n"
|
||||
" forEach, version 1.0\n"
|
||||
" Copyright 2006, Dean Edwards\n"
|
||||
" License: http://www.opensource.org/licenses/mit-license.php\n"
|
||||
"*/\n"
|
||||
"\n"
|
||||
"// array-like enumeration\n"
|
||||
"if (!Array.forEach) { // mozilla already supports this\n"
|
||||
" Array.forEach = function(array, block, context) {\n"
|
||||
" for (var i = 0; i < array.length; i++) {\n"
|
||||
" block.call(context, array[i], i, array);\n"
|
||||
" }\n"
|
||||
" };\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"// generic enumeration\n"
|
||||
"Function.prototype.forEach = function(object, block, context) {\n"
|
||||
" for (var key in object) {\n"
|
||||
" if (typeof this.prototype[key] == \"undefined\") {\n"
|
||||
" block.call(context, object[key], key, object);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"// character enumeration\n"
|
||||
"String.forEach = function(string, block, context) {\n"
|
||||
" Array.forEach(string.split(\"\"), function(chr, index) {\n"
|
||||
" block.call(context, chr, index, string);\n"
|
||||
" });\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"// globally resolve forEach enumeration\n"
|
||||
"var forEach = function(object, block, context) {\n"
|
||||
" if (object) {\n"
|
||||
" var resolve = Object; // default\n"
|
||||
" if (object instanceof Function) {\n"
|
||||
" // functions have a \"length\" property\n"
|
||||
" resolve = Function;\n"
|
||||
" } else if (object.forEach instanceof Function) {\n"
|
||||
" // the object implements a custom forEach method so use that\n"
|
||||
" object.forEach(block, context);\n"
|
||||
" return;\n"
|
||||
" } else if (typeof object == \"string\") {\n"
|
||||
" // the object is a string\n"
|
||||
" resolve = String;\n"
|
||||
" } else if (typeof object.length == \"number\") {\n"
|
||||
" // the object is array-like\n"
|
||||
" resolve = Array;\n"
|
||||
" }\n"
|
||||
" resolve.forEach(object, block, context);\n"
|
||||
" }\n"
|
||||
"};";
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,36 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BUILTIN_SORTTABLE_JS_H
|
||||
#define BRPC_BUILTIN_SORTTABLE_JS_H
|
||||
|
||||
#include "butil/iobuf.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Get the sorttable.js as string or IOBuf.
|
||||
// We need to pack all js inside C++ code so that builtin services can be
|
||||
// accessed without external resources and network connection.
|
||||
const char* sorttable_js();
|
||||
const butil::IOBuf& sorttable_js_iobuf();
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_BUILTIN_SORTTABLE_JS_H
|
||||
@@ -0,0 +1,272 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <ostream>
|
||||
#include <google/protobuf/descriptor.h> // ServiceDescriptor
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/server.h" // Server
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/details/method_status.h" // MethodStatus
|
||||
#include "brpc/builtin/status_service.h"
|
||||
#include "brpc/nshead_service.h" // NsheadService
|
||||
#ifdef ENABLE_THRIFT_FRAMED_PROTOCOL
|
||||
#include "brpc/thrift_service.h" // ThriftService
|
||||
#endif
|
||||
#include "brpc/rtmp.h" // RtmpService
|
||||
#include "brpc/builtin/common.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
namespace policy {
|
||||
extern MethodStatus* g_client_msg_status;
|
||||
extern MethodStatus* g_server_msg_status;
|
||||
}
|
||||
|
||||
// Defined in vars_service.cpp
|
||||
void PutVarsHeading(std::ostream& os, bool expand_all);
|
||||
|
||||
void StatusService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::StatusRequest*,
|
||||
::brpc::StatusResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
const Server* server = cntl->server();
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
|
||||
// NOTE: the plain output also fits format of public/configure so that user
|
||||
// can load values more easily.
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
butil::IOBufBuilder os;
|
||||
std::string str;
|
||||
if (use_html) {
|
||||
os << "<!DOCTYPE html><html><head>\n"
|
||||
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
|
||||
bool expand = cntl->http_request().uri().GetQuery("expand");
|
||||
PutVarsHeading(os, expand);
|
||||
os << "</head><body>";
|
||||
server->PrintTabsBody(os, "status");
|
||||
os << "<div class=\"layer1\">\n";
|
||||
}
|
||||
os << "version: " << server->version() << '\n';
|
||||
|
||||
// non_service_error
|
||||
if (use_html) {
|
||||
os << "<p class=\"variable\">";
|
||||
}
|
||||
os << "non_service_error: ";
|
||||
if (use_html) {
|
||||
os << "<span id=\"value-" << server->_nerror_bvar.name() << "\">";
|
||||
}
|
||||
os << server->_nerror_bvar.get_value();
|
||||
if (use_html) {
|
||||
os << "</span></p><div class=\"detail\"><div id=\"" << server->_nerror_bvar.name()
|
||||
<< "\" class=\"flot-placeholder\"></div></div>";
|
||||
}
|
||||
os << '\n';
|
||||
|
||||
// connection_count
|
||||
if (use_html) {
|
||||
os << "<p class=\"variable\">";
|
||||
}
|
||||
os << "connection_count: ";
|
||||
if (use_html) {
|
||||
os << "<span id=\"value-" << server->ServerPrefix()
|
||||
<< "_connection_count\">";
|
||||
}
|
||||
ServerStatistics ss;
|
||||
server->GetStat(&ss);
|
||||
os << ss.connection_count;
|
||||
if (use_html) {
|
||||
os << "</span></p><div class=\"detail\"><div id=\""
|
||||
<< server->ServerPrefix()
|
||||
<< "_connection_count\" class=\"flot-placeholder\"></div></div>";
|
||||
}
|
||||
os << '\n';
|
||||
|
||||
// max_concurrency
|
||||
os << "max_concurrency: ";
|
||||
const int mc = server->options().max_concurrency;
|
||||
if (mc <= 0) {
|
||||
os << "unlimited";
|
||||
} else {
|
||||
os << mc;
|
||||
}
|
||||
os << '\n';
|
||||
|
||||
// concurrency
|
||||
if (use_html) {
|
||||
os << "<p class=\"variable\">";
|
||||
}
|
||||
os << "concurrency: ";
|
||||
if (use_html) {
|
||||
os << "<span id=\"value-" << server->ServerPrefix()
|
||||
<< "_concurrency\">";
|
||||
}
|
||||
os << server->Concurrency();
|
||||
if (use_html) {
|
||||
os << "</span></p><div class=\"detail\"><div id=\""
|
||||
<< server->ServerPrefix()
|
||||
<< "_concurrency\" class=\"flot-placeholder\"></div></div>";
|
||||
}
|
||||
os << '\n';
|
||||
|
||||
|
||||
const Server::ServiceMap &services = server->_fullname_service_map;
|
||||
std::ostringstream desc;
|
||||
DescribeOptions desc_options;
|
||||
desc_options.verbose = true;
|
||||
desc_options.use_html = use_html;
|
||||
|
||||
for (Server::ServiceMap::const_iterator
|
||||
iter = services.begin(); iter != services.end(); ++iter) {
|
||||
const Server::ServiceProperty& sp = iter->second;
|
||||
if (!sp.is_user_service()) {
|
||||
continue;
|
||||
}
|
||||
CHECK(sp.service);
|
||||
if (dynamic_cast<Tabbed*>(sp.service)) {
|
||||
// Tabbed services are probably for monitoring, their own status
|
||||
// are not important.
|
||||
continue;
|
||||
}
|
||||
const google::protobuf::ServiceDescriptor* d =
|
||||
sp.service->GetDescriptor();
|
||||
os << (use_html ? "<h3>" : "[") << d->full_name()
|
||||
<< (use_html ? "</h3>" : "]")
|
||||
<< '\n';
|
||||
|
||||
// Output customized status if the service implements Describable
|
||||
Describable* obj = dynamic_cast<Describable*>(sp.service);
|
||||
if (obj) {
|
||||
desc.str("");
|
||||
obj->Describe(desc, desc_options);
|
||||
const size_t len = desc.str().size();
|
||||
if (len) {
|
||||
os << desc.str();
|
||||
if (desc.str()[len-1] != '\n') {
|
||||
os << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int method_count = d->method_count();
|
||||
for (int j = 0; j < method_count; ++j) {
|
||||
const google::protobuf::MethodDescriptor* md = d->method(j);
|
||||
Server::MethodProperty* mp =
|
||||
server->_method_map.seek(d->method(j)->full_name());
|
||||
if (use_html) {
|
||||
os << "<h4>" << md->name()
|
||||
<< " (<a href=\"/protobufs/" << md->input_type()->full_name()
|
||||
<< "\">" << md->input_type()->name() << "</a>"
|
||||
<< ") returns (<a href=\"/protobufs/"
|
||||
<< md->output_type()->full_name()
|
||||
<< "\">" << md->output_type()->name() << "</a>)";
|
||||
if (mp) {
|
||||
if (mp->http_url) {
|
||||
os << " @" << *mp->http_url;
|
||||
}
|
||||
}
|
||||
os << "</h4>\n";
|
||||
} else {
|
||||
os << "\n" << md->name() << " (" << md->input_type()->name()
|
||||
<< ") returns (" << md->output_type()->name() << ")";
|
||||
if (mp) {
|
||||
if (mp->http_url) {
|
||||
os << " @" << *mp->http_url;
|
||||
}
|
||||
}
|
||||
os << '\n';
|
||||
}
|
||||
if (mp && mp->status) {
|
||||
mp->status->Describe(os, desc_options);
|
||||
os << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
const BaiduMasterService* baidu_master_service = server->options().baidu_master_service;
|
||||
if (baidu_master_service && baidu_master_service->_status) {
|
||||
DescribeOptions options;
|
||||
options.verbose = false;
|
||||
options.use_html = use_html;
|
||||
os << (use_html ? "<h3>" : "[");
|
||||
baidu_master_service->Describe(os, options);
|
||||
os << (use_html ? "</h3>\n" : "]\n");
|
||||
baidu_master_service->_status->Describe(os, desc_options);
|
||||
os << '\n';
|
||||
}
|
||||
const NsheadService* nshead_svc = server->options().nshead_service;
|
||||
if (nshead_svc && nshead_svc->_status) {
|
||||
DescribeOptions options;
|
||||
options.verbose = false;
|
||||
options.use_html = use_html;
|
||||
os << (use_html ? "<h3>" : "[");
|
||||
nshead_svc->Describe(os, options);
|
||||
os << (use_html ? "</h3>\n" : "]\n");
|
||||
nshead_svc->_status->Describe(os, desc_options);
|
||||
os << '\n';
|
||||
}
|
||||
#ifdef ENABLE_THRIFT_FRAMED_PROTOCOL
|
||||
const ThriftService* thrift_svc = server->options().thrift_service;
|
||||
if (thrift_svc && thrift_svc->_status) {
|
||||
DescribeOptions options;
|
||||
options.verbose = false;
|
||||
options.use_html = use_html;
|
||||
os << (use_html ? "<h3>" : "[");
|
||||
thrift_svc->Describe(os, options);
|
||||
os << (use_html ? "</h3>\n" : "]\n");
|
||||
thrift_svc->_status->Describe(os, desc_options);
|
||||
os << '\n';
|
||||
}
|
||||
#endif
|
||||
if (policy::g_server_msg_status) {
|
||||
DescribeOptions options;
|
||||
options.verbose = false;
|
||||
options.use_html = use_html;
|
||||
os << (use_html ? "<h3>" : "[")
|
||||
<< "RtmpServer Messages (in)"
|
||||
<< (use_html ? "</h3>\n" : "]\n");
|
||||
policy::g_server_msg_status->Describe(os, desc_options);
|
||||
os << '\n';
|
||||
}
|
||||
if (policy::g_client_msg_status) {
|
||||
DescribeOptions options;
|
||||
options.verbose = false;
|
||||
options.use_html = use_html;
|
||||
os << (use_html ? "<h3>" : "[")
|
||||
<< "RtmpClient Messages (in)"
|
||||
<< (use_html ? "</h3>\n" : "]\n");
|
||||
policy::g_client_msg_status->Describe(os, desc_options);
|
||||
os << '\n';
|
||||
}
|
||||
|
||||
if (use_html) {
|
||||
os << "</div></body></html>";
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
cntl->set_response_compress_type(COMPRESS_TYPE_GZIP);
|
||||
}
|
||||
|
||||
void StatusService::GetTabInfo(TabInfoList* info_list) const {
|
||||
TabInfo* info = info_list->add();
|
||||
info->path = "/status";
|
||||
info->tab_name = "status";
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,43 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_STATUS_SERVICE_H
|
||||
#define BRPC_STATUS_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
#include "brpc/builtin/tabbed.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class Server;
|
||||
|
||||
class StatusService : public status, public Tabbed {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::StatusRequest* request,
|
||||
::brpc::StatusResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void GetTabInfo(TabInfoList* info_list) const;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif //BRPC_STATUS_SERVICE_H
|
||||
@@ -0,0 +1,88 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BUILTIN_TABBED_H
|
||||
#define BRPC_BUILTIN_TABBED_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Contain the information for showing a tab.
|
||||
struct TabInfo {
|
||||
std::string tab_name;
|
||||
std::string path;
|
||||
|
||||
bool valid() const { return !tab_name.empty() && !path.empty(); }
|
||||
};
|
||||
|
||||
// For appending TabInfo
|
||||
class TabInfoList {
|
||||
public:
|
||||
TabInfoList() {}
|
||||
TabInfo* add() {
|
||||
_list.push_back(TabInfo());
|
||||
return &_list[_list.size() - 1];
|
||||
}
|
||||
size_t size() const { return _list.size(); }
|
||||
const TabInfo& operator[](size_t i) const { return _list[i]; }
|
||||
void resize(size_t newsize) { _list.resize(newsize); }
|
||||
private:
|
||||
TabInfoList(const TabInfoList&);
|
||||
void operator=(const TabInfoList&);
|
||||
std::vector<TabInfo> _list;
|
||||
};
|
||||
|
||||
// Inherit this class to show the service with one or more tabs.
|
||||
// NOTE: tabbed services are not shown in /status.
|
||||
// Example:
|
||||
// #include <brpc/builtin/common.h>
|
||||
// ...
|
||||
// void MySerivce::GetTabInfo(brpc::TabInfoList* info_list) const {
|
||||
// brpc::TabInfo* info = info_list->add();
|
||||
// info->tab_name = "my_tab";
|
||||
// info->path = "/MyService/MyMethod";
|
||||
// }
|
||||
// void MyService::MyMethod(::google::protobuf::RpcController* controller,
|
||||
// const XXXRequest* request,
|
||||
// XXXResponse* response,
|
||||
// ::google::protobuf::Closure* done) {
|
||||
// ...
|
||||
// if (use_html) {
|
||||
// os << "<!DOCTYPE html><html><head>\n"
|
||||
// << "<script language=\"javascript\" type=\"text/javascript\" src=\"/js/jquery_min\"></script>\n"
|
||||
// << brpc::TabsHead() << "</head><body>";
|
||||
// cntl->server()->PrintTabsBody(os, "my_tab");
|
||||
// }
|
||||
// ...
|
||||
// if (use_html) {
|
||||
// os << "</body></html>";
|
||||
// }
|
||||
// }
|
||||
// Note: don't forget the jquery.
|
||||
class Tabbed {
|
||||
public:
|
||||
virtual ~Tabbed() = default;
|
||||
virtual void GetTabInfo(TabInfoList* info_list) const = 0;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_BUILTIN_TABBED_H
|
||||
@@ -0,0 +1,53 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include "butil/time.h"
|
||||
#include "butil/logging.h"
|
||||
#include "butil/popen.h"
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/builtin/threads_service.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "butil/string_printf.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
void ThreadsService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::ThreadsRequest*,
|
||||
::brpc::ThreadsResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
butil::IOBuf& resp = cntl->response_attachment();
|
||||
|
||||
std::string cmd = butil::string_printf("pstack %lld", (long long)getpid());
|
||||
butil::Timer tm;
|
||||
tm.start();
|
||||
butil::IOBufBuilder pstack_output;
|
||||
const int rc = butil::read_command_output(pstack_output, cmd.c_str());
|
||||
if (rc < 0) {
|
||||
LOG(ERROR) << "Fail to popen `" << cmd << "'";
|
||||
return;
|
||||
}
|
||||
pstack_output.move_to(resp);
|
||||
tm.stop();
|
||||
resp.append(butil::string_printf("\n\ntime=%" PRId64 "ms", tm.m_elapsed()));
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,39 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_THREADS_SERVICE_H
|
||||
#define BRPC_THREADS_SERVICE_H
|
||||
|
||||
#include <ostream>
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class ThreadsService : public threads {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::ThreadsRequest* request,
|
||||
::brpc::ThreadsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif //BRPC_THREADS_SERVICE_H
|
||||
@@ -0,0 +1,438 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <ostream>
|
||||
#include <vector> // std::vector
|
||||
#include "butil/string_splitter.h"
|
||||
#include "bvar/bvar.h"
|
||||
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/errno.pb.h"
|
||||
#include "brpc/server.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
#include "brpc/builtin/vars_service.h"
|
||||
|
||||
namespace bvar {
|
||||
DECLARE_bool(quote_vector);
|
||||
}
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// TODO(gejun): parameterize.
|
||||
// This function returns the script to make bvar plot-able.
|
||||
// The idea: flot graphs were attached to plot-able bvar as the next <div>
|
||||
// when the html was generated. When user clicks a bvar, send a request to
|
||||
// server to get the value series of the bvar. When the response comes back,
|
||||
// plot and show the graph. Requests will be sent to server every 1 second
|
||||
// until user clicks the bvar and hide the graph.
|
||||
void PutVarsHeading(std::ostream& os, bool expand_all) {
|
||||
os << "<script language=\"javascript\" type=\"text/javascript\" src=\"/js/jquery_min\"></script>\n"
|
||||
"<script language=\"javascript\" type=\"text/javascript\" src=\"/js/flot_min\"></script>\n"
|
||||
<< TabsHead()
|
||||
<< "<style type=\"text/css\">\n"
|
||||
"#layer1 { margin:0; padding:0; width:1111px; }\n"
|
||||
// style of plot-able bvar
|
||||
".variable {\n"
|
||||
" margin:0px;\n"
|
||||
" color:#000000;\n"
|
||||
" cursor:pointer;\n"
|
||||
" position:relative;\n"
|
||||
" background-color:#ffffff;\n"
|
||||
"}\n"
|
||||
// style of non-plot-able bvar, the difference with .variable is only
|
||||
// the cursor .
|
||||
".nonplot-variable {\n"
|
||||
" margin:0px;\n"
|
||||
" color:#000000;\n"
|
||||
" position:relative;\n"
|
||||
" background-color:#ffffff;\n"
|
||||
"}\n"
|
||||
// style of <p>
|
||||
"p {padding: 2px 0; margin: 0px; }\n"
|
||||
// style of container of flot graph.
|
||||
".detail {\n"
|
||||
" margin: 0px;\n"
|
||||
" width: 800px;\n"
|
||||
" background-color:#fafafa;\n"
|
||||
"}\n"
|
||||
".flot-placeholder {\n"
|
||||
" width: 800px;\n"
|
||||
" height: 200px;\n"
|
||||
" line-height: 1.2em;\n"
|
||||
"}\n"
|
||||
"</style>\n"
|
||||
|
||||
"<script type=\"text/javascript\">\n"
|
||||
// Mark if a bvar was ever clicked.
|
||||
"var everEnabled = {}\n"
|
||||
// Mark if a bvar was enabled plotting
|
||||
"var enabled = {}\n"
|
||||
// the bvar under cursor
|
||||
"var hovering_var = \"\"\n"
|
||||
// timeout id of last server call.
|
||||
"var timeoutId = {}\n"
|
||||
// last plot of the bvar.
|
||||
"var lastPlot = {}\n"
|
||||
|
||||
"function prepareGraphs() {\n"
|
||||
// Hide all graphs at first.
|
||||
" $(\".detail\").hide();\n"
|
||||
|
||||
// Register clicking functions.
|
||||
" $(\".variable\").click(function() {\n"
|
||||
" var mod = $(this).next(\".detail\");\n"
|
||||
" mod.slideToggle(\"fast\");\n"
|
||||
" var var_name = mod.children(\":first-child\").attr(\"id\");\n"
|
||||
" if (!everEnabled[var_name]) {\n"
|
||||
" everEnabled[var_name] = true;\n"
|
||||
// Create tooltip at first click.
|
||||
" $(\"<div id='tooltip-\" + var_name + \"'></div>\").css({\n"
|
||||
" position: \"absolute\",\n"
|
||||
" display: \"none\",\n"
|
||||
" border: \"1px solid #fdd\",\n"
|
||||
" padding: \"2px\",\n"
|
||||
" \"background-color\": \"#ffffca\",\n"
|
||||
" opacity: 0.80\n"
|
||||
" }).appendTo(\"body\");\n"
|
||||
// Register hovering event and show the tooltip when event occurs.
|
||||
" $(\"#\" + var_name).bind(\"plothover\", function(event, pos, item) {\n"
|
||||
" if (item) {\n"
|
||||
" hovering_var = var_name;\n"
|
||||
" var thePlot = lastPlot[var_name];\n"
|
||||
" if (thePlot != null) {\n"
|
||||
" item.series.color = \"#808080\";\n"
|
||||
" thePlot.draw();\n"
|
||||
" }\n"
|
||||
" var x = item.datapoint[0];\n"
|
||||
" var y = item.datapoint[1];\n"
|
||||
" $(\"#tooltip-\" + var_name)\n"
|
||||
" .html(y + \"<br/>(\" + describeX(x, item.series) + \")\")\n"
|
||||
" .css({top: item.pageY+5, left: item.pageX+15})\n"
|
||||
" .show();\n"
|
||||
" } else {\n"
|
||||
" hovering_var = \"\";\n"
|
||||
" $(\"#tooltip-\" + var_name).hide();\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
// Register mouseleave to make sure the tooltip is hidden when cursor
|
||||
// is out.
|
||||
" $(\"#\" + var_name).bind(\"mouseleave\", function() {\n"
|
||||
" $(\"#tooltip-\" + var_name).hide();\n"
|
||||
" });\n"
|
||||
" }\n"
|
||||
" if (!enabled[var_name]) {\n"
|
||||
" enabled[var_name] = true;\n"
|
||||
" fetchData(var_name);\n"
|
||||
" } else {\n"
|
||||
" enabled[var_name] = false;\n"
|
||||
" clearTimeout(timeoutId[var_name]);\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
<< (expand_all ?
|
||||
" $(\".variable\").click();\n" :
|
||||
// Set id to "default_expand" to make the graph expand by default.
|
||||
// E.g. latency and qps in /status page are expanded by default.
|
||||
" $(\".default_expand\").click();\n") <<
|
||||
"}\n"
|
||||
|
||||
// options for plotting.
|
||||
"var trendOptions = {\n"
|
||||
" colors: ['#F0D06E','#F0B06E','#F0A06E','#F0906E','#F0806E'],\n"
|
||||
" legend: {show:false},\n"
|
||||
" grid: {hoverable:true},\n"
|
||||
" xaxis: { \n"
|
||||
" \"ticks\": [[29,\"-1 day\"],[53,\"-1 hour\"],[113,\"-1 minute\"]]\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"var cdfOptions = {\n"
|
||||
" grid: {hoverable: true},\n"
|
||||
" lines: {\n"
|
||||
" show: true,\n"
|
||||
" fill: true\n"
|
||||
" },\n"
|
||||
" xaxis: {\n"
|
||||
" \"ticks\": [[10,\"10%\"],[20,\"20%\"],[30,\"30%\"],[40,\"40%\"]\n"
|
||||
" ,[50,\"50%\"],[60,\"60%\"],[70,\"70%\"],[80,\"80%\"]\n"
|
||||
" ,[90,\"90%\"],[101,\"99.99%\"]]\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
|
||||
// Show x in tooltip intuitively.
|
||||
"function describeTrendX(x) {\n"
|
||||
" if (x >= 173) {\n"
|
||||
" return \"just now\";\n"
|
||||
" } else if (x > 113) {\n"
|
||||
" return (x - 173) + \" second\";\n"
|
||||
" } else if (x > 53) {\n"
|
||||
" return (x - 114) + \" minute\";\n"
|
||||
" } else if (x > 29) {\n"
|
||||
" return (x - 54) + \" hour\";\n"
|
||||
" } else {\n"
|
||||
" return (x - 30) + \" day\";\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function describeCDFX(x) {\n"
|
||||
" if (x <= 99) {\n"
|
||||
" return x + '%';\n"
|
||||
" } else if (x == 100) {\n"
|
||||
" return '99.9%';\n"
|
||||
" } else if (x == 101) {\n"
|
||||
" return '99.99%';\n"
|
||||
" } else {\n"
|
||||
" return 'unknown ' + x;\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function describeX(x, series) {\n"
|
||||
" if (series.data[series.data.length-1][0] == 173) {\n"
|
||||
" if (series.label != null) {\n"
|
||||
" return series.label + ' ' + describeTrendX(x);\n"
|
||||
" } else {\n"
|
||||
" return describeTrendX(x);\n"
|
||||
" }\n"
|
||||
" } else if (series.data[series.data.length-1][0] == 101) {\n"
|
||||
" return describeCDFX(x);\n"
|
||||
" } else {\n"
|
||||
" return x;\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
// Get value series of bvar from server.
|
||||
"function fetchData(var_name) {\n"
|
||||
" function onDataReceived(series) {\n"
|
||||
" if (hovering_var != var_name) {\n"
|
||||
" if (series.label == 'trend') {\n"
|
||||
" lastPlot[var_name] = $.plot(\"#\" + var_name, [series.data], trendOptions);\n"
|
||||
" $(\"#value-\" + var_name).html(series.data[series.data.length - 1][1]);\n"
|
||||
" } else if (series.label == 'cdf') {\n"
|
||||
" lastPlot[var_name] = $.plot(\"#\" + var_name, [series.data], cdfOptions);\n"
|
||||
" $(\"#value-\" + var_name).html(series.data[series.data.length - 1][1]);\n"
|
||||
" } else {\n"
|
||||
" lastPlot[var_name] = $.plot(\"#\" + var_name, series, trendOptions);\n"
|
||||
<< (bvar::FLAGS_quote_vector ?
|
||||
" var newValue = '\"[';\n" :
|
||||
" var newValue = '[';\n") <<
|
||||
" var i;\n"
|
||||
" for (i = 0; i < series.length; ++i) {\n"
|
||||
" if (i != 0) newValue += ',';\n"
|
||||
" var data = series[i].data;\n"
|
||||
" newValue += data[data.length - 1][1];\n"
|
||||
" }\n"
|
||||
<< (bvar::FLAGS_quote_vector ?
|
||||
" newValue += ']\"';\n" :
|
||||
" newValue += ']';\n") <<
|
||||
" $(\"#value-\" + var_name).html(newValue);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" $.ajax({\n"
|
||||
" url: \"/vars/\" + var_name + \"?series\",\n"
|
||||
" type: \"GET\",\n"
|
||||
" dataType: \"json\",\n"
|
||||
" success: onDataReceived\n"
|
||||
" });\n"
|
||||
" if (enabled[var_name]) {\n"
|
||||
" timeoutId[var_name] = setTimeout(function(){ fetchData(var_name); }, 1000);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"$(prepareGraphs);\n"
|
||||
"</script>\n";
|
||||
}
|
||||
|
||||
// We need the space before colon so that user does not have to remove
|
||||
// trailing colon from $1
|
||||
static const std::string VAR_SEP = " : ";
|
||||
|
||||
class VarsDumper : public bvar::Dumper {
|
||||
public:
|
||||
explicit VarsDumper(butil::IOBufBuilder& os, bool use_html)
|
||||
: _os(os), _use_html(use_html) {}
|
||||
|
||||
bool dump(const std::string& name, const butil::StringPiece& desc) {
|
||||
bool plot = false;
|
||||
if (_use_html) {
|
||||
bvar::SeriesOptions series_options;
|
||||
series_options.test_only = true;
|
||||
const int rc = bvar::Variable::describe_series_exposed(
|
||||
name, _os, series_options);
|
||||
plot = (rc == 0);
|
||||
if (plot) {
|
||||
_os << "<p class=\"variable\">";
|
||||
} else {
|
||||
_os << "<p class=\"nonplot-variable\">";
|
||||
}
|
||||
}
|
||||
_os << name << VAR_SEP;
|
||||
if (_use_html) {
|
||||
_os << "<span id=\"value-" << name << "\">";
|
||||
}
|
||||
_os << desc;
|
||||
if (_use_html) {
|
||||
_os << "</span></p>\n";
|
||||
if (plot) {
|
||||
_os << "<div class=\"detail\"><div id=\"" << name
|
||||
<< "\" class=\"flot-placeholder\"></div></div>\n";
|
||||
}
|
||||
} else {
|
||||
_os << "\r\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void move_to(butil::IOBuf& buf) {
|
||||
_os.move_to(buf);
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(VarsDumper);
|
||||
|
||||
butil::IOBufBuilder & _os;
|
||||
bool _use_html;
|
||||
};
|
||||
|
||||
void VarsService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::VarsRequest*,
|
||||
::brpc::VarsResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
if (cntl->http_request().uri().GetQuery("series") != NULL) {
|
||||
butil::IOBufBuilder os;
|
||||
bvar::SeriesOptions series_options;
|
||||
const int rc = bvar::Variable::describe_series_exposed(
|
||||
cntl->http_request().unresolved_path(), os, series_options);
|
||||
if (rc == 0) {
|
||||
cntl->http_response().set_content_type("application/json");
|
||||
os.move_to(cntl->response_attachment());
|
||||
} else if (rc < 0) {
|
||||
cntl->SetFailed(ENOMETHOD, "Fail to find any bvar by `%s'",
|
||||
cntl->http_request().unresolved_path().c_str());
|
||||
} else {
|
||||
cntl->SetFailed(ENODATA, "`%s' does not have value series",
|
||||
cntl->http_request().unresolved_path().c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
bool with_tabs = false;
|
||||
if (use_html && cntl->http_request().uri().GetQuery("dataonly") == NULL) {
|
||||
with_tabs = true;
|
||||
}
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
|
||||
butil::IOBufBuilder os;
|
||||
if (with_tabs) {
|
||||
os << "<!DOCTYPE html><html><head>\n"
|
||||
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
|
||||
PutVarsHeading(os, cntl->http_request().uri().GetQuery("expand"));
|
||||
os << "<script type=\"text/javascript\">\n"
|
||||
"const delayTime = 200;\n"
|
||||
"var searching = false;\n"
|
||||
"function toURL(text) {\n"
|
||||
" if (text == '') {\n"
|
||||
" return '/vars';\n"
|
||||
" }\n"
|
||||
// Normalize ;,\s\* to space, trim beginning/ending spaces and
|
||||
// replace all spaces with *;* and add beginning/ending *
|
||||
// iobuf,bthread -> *iobuf*;*bthread*
|
||||
// iobuf, -> *iobuf*
|
||||
// ;,iobuf -> *iobuf*
|
||||
// ,;*iobuf*, bthread;,; -> *iobuf*;*bthread*
|
||||
" text = text.replace(/(;|,|\\s|\\*)+/g, ' ').trim()"
|
||||
" .replace(/ /g, '*;*');\n"
|
||||
" if (text == '') {\n"
|
||||
" return '/vars';\n"
|
||||
" }\n"
|
||||
" return '/vars/*' + text + '*';\n"
|
||||
"}\n"
|
||||
"function onDataReceived(searchText, data) {\n"
|
||||
" for (var var_name in enabled) {\n"
|
||||
" if (enabled[var_name]) {\n"
|
||||
" clearTimeout(timeoutId[var_name]);\n"
|
||||
" }\n"
|
||||
" enabled = {};\n"
|
||||
" everEnabled = {};\n"
|
||||
" }\n"
|
||||
" $(\".detail\").hide();\n"
|
||||
" $('#layer1').html(data);\n"
|
||||
" prepareGraphs();\n"
|
||||
" window.history.pushState('', '', toURL(searchText));\n"
|
||||
" var newSearchText = $('#searchbox').val();\n"
|
||||
" if (newSearchText != searchText) {\n"
|
||||
" setTimeout(search, delayTime);\n"
|
||||
" console.log('text changed during searching, search again');\n"
|
||||
" } else {\n"
|
||||
" searching = false;\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function search() {\n"
|
||||
" var searchText = $('#searchbox').val();\n"
|
||||
" $.ajax({\n"
|
||||
" url: toURL(searchText) + '?dataonly',\n"
|
||||
" type: \"GET\",\n"
|
||||
" dataType: \"html\",\n"
|
||||
" success: function(data) { onDataReceived(searchText, data); },\n"
|
||||
" error: function(xhr, ajaxOptions, thrownError) {\n"
|
||||
" onDataReceived(searchText, xhr.responseText);\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
"}\n"
|
||||
"function onQueryChanged() {\n"
|
||||
" if (searching) {\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" searching = true;\n"
|
||||
" setTimeout(search, delayTime);\n"
|
||||
"}\n"
|
||||
"</script>\n"
|
||||
"</head>\n<body>\n";
|
||||
cntl->server()->PrintTabsBody(os, "vars");
|
||||
os << "<p>Search : <input id='searchbox' type='text'"
|
||||
" onkeyup='onQueryChanged()'></p>"
|
||||
"<div id=\"layer1\">\n";
|
||||
}
|
||||
VarsDumper dumper(os, use_html);
|
||||
bvar::DumpOptions options;
|
||||
options.question_mark = '$';
|
||||
options.display_filter =
|
||||
(use_html ? bvar::DISPLAY_ON_HTML : bvar::DISPLAY_ON_PLAIN_TEXT);
|
||||
options.white_wildcards = cntl->http_request().unresolved_path();
|
||||
const int ndump = bvar::Variable::dump_exposed(&dumper, &options);
|
||||
if (ndump < 0) {
|
||||
cntl->SetFailed("Fail to dump vars");
|
||||
return;
|
||||
}
|
||||
if (!options.white_wildcards.empty() && ndump == 0) {
|
||||
cntl->SetFailed(ENOMETHOD, "Fail to find any bvar by `%s'",
|
||||
options.white_wildcards.c_str());
|
||||
}
|
||||
if (with_tabs) {
|
||||
os << "</div></body></html>";
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
cntl->set_response_compress_type(COMPRESS_TYPE_GZIP);
|
||||
}
|
||||
|
||||
void VarsService::GetTabInfo(TabInfoList* info_list) const {
|
||||
TabInfo* info = info_list->add();
|
||||
info->path = "/vars";
|
||||
info->tab_name = "vars";
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,41 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_VARS_SERVICE_H
|
||||
#define BRPC_VARS_SERVICE_H
|
||||
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
#include "brpc/builtin/tabbed.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class VarsService : public vars, public Tabbed {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::VarsRequest* request,
|
||||
::brpc::VarsResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
void GetTabInfo(TabInfoList* info_list) const;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_VARS_SERVICE_H
|
||||
@@ -0,0 +1,41 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/server.h" // Server
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/builtin/version_service.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
void VersionService::default_method(::google::protobuf::RpcController* controller,
|
||||
const ::brpc::VersionRequest*,
|
||||
::brpc::VersionResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = (Controller *)controller;
|
||||
cntl->http_response().set_content_type("text/plain");
|
||||
if (_server->version().empty()) {
|
||||
cntl->response_attachment().append("unknown");
|
||||
} else {
|
||||
cntl->response_attachment().append(_server->version());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,45 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_VERSION_SERVICE_H
|
||||
#define BRPC_VERSION_SERVICE_H
|
||||
|
||||
#include <ostream>
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class Server;
|
||||
|
||||
class VersionService : public version {
|
||||
public:
|
||||
explicit VersionService(Server* server) : _server(server) {}
|
||||
|
||||
void default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::VersionRequest* request,
|
||||
::brpc::VersionResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
private:
|
||||
Server* _server;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif //BRPC_VERSION_SERVICE_H
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,37 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_BUILTIN_VIZ_MIN_JS_H
|
||||
#define BRPC_BUILTIN_VIZ_MIN_JS_H
|
||||
|
||||
#include "butil/iobuf.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Get the viz.min.js as string or IOBuf.
|
||||
// We need to pack all js inside C++ code so that builtin services can be
|
||||
// accessed without external resources and network connection.
|
||||
const char* viz_min_js();
|
||||
const butil::IOBuf& viz_min_js_iobuf();
|
||||
const butil::IOBuf& viz_min_js_iobuf_gzip();
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_BUILTIN_VIZ_MIN_JS_H
|
||||
@@ -0,0 +1,99 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#if !BRPC_WITH_GLOG
|
||||
|
||||
#include "brpc/log.h"
|
||||
#include "brpc/controller.h" // Controller
|
||||
#include "brpc/closure_guard.h" // ClosureGuard
|
||||
#include "brpc/builtin/vlog_service.h"
|
||||
#include "brpc/builtin/common.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class VLogPrinter : public VLogSitePrinter {
|
||||
public:
|
||||
VLogPrinter(bool use_html, std::ostream& os)
|
||||
: _use_html(use_html), _os(&os) { }
|
||||
|
||||
void print(const VLogSitePrinter::Site& site) {
|
||||
const char* const bar = (_use_html ? "</td><td>" : " | ");
|
||||
if (_use_html) {
|
||||
*_os << "<tr><td>";
|
||||
}
|
||||
*_os << site.full_module << ":" << site.line_no << bar
|
||||
<< site.current_verbose_level << bar << site.required_verbose_level
|
||||
<< bar;
|
||||
if (site.current_verbose_level >= site.required_verbose_level) {
|
||||
if (_use_html) {
|
||||
*_os << "<span style='font-weight:bold;color:#00A000'>"
|
||||
<< "enabled</span>";
|
||||
} else {
|
||||
*_os << "enabled";
|
||||
}
|
||||
} else {
|
||||
*_os << "disabled";
|
||||
}
|
||||
if (_use_html) {
|
||||
*_os << "</td></tr>";
|
||||
}
|
||||
*_os << '\n';
|
||||
}
|
||||
|
||||
private:
|
||||
bool _use_html;
|
||||
std::ostream* _os;
|
||||
|
||||
};
|
||||
|
||||
void VLogService::default_method(::google::protobuf::RpcController* cntl_base,
|
||||
const ::brpc::VLogRequest*,
|
||||
::brpc::VLogResponse*,
|
||||
::google::protobuf::Closure* done) {
|
||||
ClosureGuard done_guard(done);
|
||||
Controller *cntl = static_cast<Controller*>(cntl_base);
|
||||
const bool use_html = UseHTML(cntl->http_request());
|
||||
butil::IOBufBuilder os;
|
||||
|
||||
cntl->http_response().set_content_type(
|
||||
use_html ? "text/html" : "text/plain");
|
||||
if (use_html) {
|
||||
os << "<!DOCTYPE html><html><head>" << gridtable_style()
|
||||
<< "<script src=\"/js/sorttable\"></script></head><body>"
|
||||
"<table class=\"gridtable\" border=\"1\"><tr>"
|
||||
"<th>Module</th><th>Current</th><th>Required</th>"
|
||||
"<th>Status</th></tr>\n";
|
||||
} else {
|
||||
os << "Module | Current | Required | Status\n";
|
||||
}
|
||||
VLogPrinter printer(use_html, os);
|
||||
print_vlog_sites(&printer);
|
||||
if (use_html) {
|
||||
os << "</table>\n";
|
||||
}
|
||||
|
||||
if (use_html) {
|
||||
os << "</body></html>\n";
|
||||
}
|
||||
os.move_to(cntl->response_attachment());
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_VLOG_SERVICE_H
|
||||
#define BRPC_VLOG_SERVICE_H
|
||||
|
||||
#if !BRPC_WITH_GLOG
|
||||
|
||||
#include <ostream>
|
||||
#include "brpc/builtin_service.pb.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class VLogService : public vlog {
|
||||
public:
|
||||
void default_method(::google::protobuf::RpcController* controller,
|
||||
const ::brpc::VLogRequest* request,
|
||||
::brpc::VLogResponse* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif // BRPC_WITH_GLOG
|
||||
|
||||
#endif //BRPC_VLOG_SERVICE_H
|
||||
@@ -0,0 +1,179 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
syntax="proto2";
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
package brpc;
|
||||
|
||||
option cc_generic_services = true;
|
||||
option java_generic_services = true;
|
||||
option java_package = "com.brpc";
|
||||
option java_outer_classname = "BuiltinService";
|
||||
|
||||
message IndexRequest {}
|
||||
message IndexResponse {}
|
||||
message FlagsRequest {}
|
||||
message FlagsResponse {}
|
||||
message VersionRequest {}
|
||||
message VersionResponse {}
|
||||
message HealthRequest {}
|
||||
message HealthResponse {}
|
||||
message StatusRequest {}
|
||||
message StatusResponse {}
|
||||
message ProtobufsRequest {}
|
||||
message ProtobufsResponse {}
|
||||
message ConnectionsRequest {}
|
||||
message ConnectionsResponse {}
|
||||
message ListRequest {}
|
||||
message ListResponse {
|
||||
repeated google.protobuf.ServiceDescriptorProto service = 1;
|
||||
}
|
||||
message VarsRequest {}
|
||||
message VarsResponse {}
|
||||
message BthreadsRequest {}
|
||||
message BthreadsResponse {}
|
||||
message IdsRequest {}
|
||||
message IdsResponse{}
|
||||
message SocketsRequest {}
|
||||
message SocketsResponse {}
|
||||
message RpczRequest {}
|
||||
message RpczResponse {}
|
||||
message ThreadsRequest {}
|
||||
message ThreadsResponse {}
|
||||
message DirRequest {}
|
||||
message DirResponse {}
|
||||
message VLogRequest {}
|
||||
message VLogResponse {}
|
||||
message MetricsRequest {}
|
||||
message MetricsResponse {}
|
||||
message MemoryRequest {}
|
||||
message MemoryResponse {}
|
||||
message BadMethodRequest {
|
||||
required string service_name = 1;
|
||||
}
|
||||
message BadMethodResponse {}
|
||||
|
||||
service index {
|
||||
rpc default_method(IndexRequest) returns (IndexResponse);
|
||||
}
|
||||
|
||||
service version {
|
||||
rpc default_method(VersionRequest) returns (VersionResponse);
|
||||
}
|
||||
|
||||
service health {
|
||||
rpc default_method(HealthRequest) returns (HealthResponse);
|
||||
}
|
||||
|
||||
service status {
|
||||
rpc default_method(StatusRequest) returns (StatusResponse);
|
||||
}
|
||||
|
||||
service protobufs {
|
||||
rpc default_method(ProtobufsRequest) returns (ProtobufsResponse);
|
||||
}
|
||||
|
||||
service connections {
|
||||
rpc default_method(ConnectionsRequest) returns (ConnectionsResponse);
|
||||
}
|
||||
|
||||
service list {
|
||||
rpc default_method(ListRequest) returns (ListResponse);
|
||||
}
|
||||
|
||||
service threads {
|
||||
rpc default_method(ThreadsRequest) returns (ThreadsResponse);
|
||||
}
|
||||
|
||||
service vlog {
|
||||
rpc default_method(VLogRequest) returns (VLogResponse);
|
||||
}
|
||||
|
||||
service bthreads {
|
||||
rpc default_method(BthreadsRequest) returns (BthreadsResponse);
|
||||
}
|
||||
|
||||
service ids {
|
||||
rpc default_method(IdsRequest) returns (IdsResponse);
|
||||
}
|
||||
|
||||
service sockets {
|
||||
rpc default_method(SocketsRequest) returns (SocketsResponse);
|
||||
}
|
||||
|
||||
service brpc_metrics {
|
||||
rpc default_method(MetricsRequest) returns (MetricsResponse);
|
||||
}
|
||||
|
||||
service badmethod {
|
||||
rpc no_method(BadMethodRequest) returns (BadMethodResponse);
|
||||
}
|
||||
|
||||
|
||||
message ProfileRequest {}
|
||||
message ProfileResponse {}
|
||||
|
||||
service pprof {
|
||||
rpc profile(ProfileRequest) returns (ProfileResponse);
|
||||
rpc contention(ProfileRequest) returns (ProfileResponse);
|
||||
rpc heap(ProfileRequest) returns (ProfileResponse);
|
||||
rpc symbol(ProfileRequest) returns (ProfileResponse);
|
||||
rpc cmdline(ProfileRequest) returns (ProfileResponse);
|
||||
rpc growth(ProfileRequest) returns (ProfileResponse);
|
||||
}
|
||||
|
||||
message HotspotsRequest {}
|
||||
message HotspotsResponse {}
|
||||
|
||||
service hotspots {
|
||||
rpc cpu(HotspotsRequest) returns (HotspotsResponse);
|
||||
rpc cpu_non_responsive(HotspotsRequest) returns (HotspotsResponse);
|
||||
rpc heap(HotspotsRequest) returns (HotspotsResponse);
|
||||
rpc heap_non_responsive(HotspotsRequest) returns (HotspotsResponse);
|
||||
rpc growth(HotspotsRequest) returns (HotspotsResponse);
|
||||
rpc growth_non_responsive(HotspotsRequest) returns (HotspotsResponse);
|
||||
rpc contention(HotspotsRequest) returns (HotspotsResponse);
|
||||
rpc contention_non_responsive(HotspotsRequest) returns (HotspotsResponse);
|
||||
rpc iobuf(HotspotsRequest) returns (HotspotsResponse);
|
||||
rpc iobuf_non_responsive(HotspotsRequest) returns (HotspotsResponse);
|
||||
}
|
||||
|
||||
service flags {
|
||||
rpc default_method(FlagsRequest) returns (FlagsResponse);
|
||||
}
|
||||
|
||||
service vars {
|
||||
rpc default_method(VarsRequest) returns (VarsResponse);
|
||||
}
|
||||
|
||||
service rpcz {
|
||||
rpc enable(RpczRequest) returns (RpczResponse);
|
||||
rpc disable(RpczRequest) returns (RpczResponse);
|
||||
rpc stats(RpczRequest) returns (RpczResponse);
|
||||
rpc hex_log_id(RpczRequest) returns (RpczResponse);
|
||||
rpc dec_log_id(RpczRequest) returns (RpczResponse);
|
||||
rpc default_method(RpczRequest) returns (RpczResponse);
|
||||
}
|
||||
|
||||
service dir {
|
||||
rpc default_method(DirRequest) returns (DirResponse);
|
||||
}
|
||||
|
||||
service memory {
|
||||
rpc default_method(MemoryRequest) returns (MemoryResponse);
|
||||
}
|
||||
+1097
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,661 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <google/protobuf/descriptor.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <memory>
|
||||
#include "butil/time.h" // milliseconds_from_now
|
||||
#include "butil/logging.h"
|
||||
#include "butil/third_party/murmurhash3/murmurhash3.h"
|
||||
#include "butil/strings/string_util.h"
|
||||
#include "bthread/unstable.h" // bthread_timer_add
|
||||
#include "brpc/socket_map.h" // SocketMapInsert
|
||||
#include "brpc/compress.h"
|
||||
#include "brpc/global.h"
|
||||
#include "brpc/span.h"
|
||||
#include "brpc/details/load_balancer_with_naming.h"
|
||||
#include "brpc/controller.h"
|
||||
#include "brpc/channel.h"
|
||||
#include "brpc/serialized_request.h"
|
||||
#include "brpc/serialized_response.h"
|
||||
#include "brpc/details/usercode_backup_pool.h" // TooManyUserCode
|
||||
#include "brpc/rdma/rdma_helper.h"
|
||||
#include "brpc/policy/esp_authenticator.h"
|
||||
#include "brpc/transport_factory.h"
|
||||
#include "brpc/details/controller_private_accessor.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
DECLARE_bool(enable_rpcz);
|
||||
DECLARE_bool(usercode_in_pthread);
|
||||
DEFINE_string(health_check_path, "", "Http path of health check call."
|
||||
"By default health check succeeds if the server is connectable."
|
||||
"If this flag is set, health check is not completed until a http "
|
||||
"call to the path succeeds within -health_check_timeout_ms(to make "
|
||||
"sure the server functions well).");
|
||||
DEFINE_int32(health_check_timeout_ms, 500, "The timeout for both establishing "
|
||||
"the connection and the http call to -health_check_path over the connection");
|
||||
|
||||
ChannelOptions::ChannelOptions()
|
||||
: connect_timeout_ms(200)
|
||||
, timeout_ms(500)
|
||||
, backup_request_ms(-1)
|
||||
, max_retry(3)
|
||||
, enable_circuit_breaker(false)
|
||||
, protocol(PROTOCOL_BAIDU_STD)
|
||||
, connection_type(CONNECTION_TYPE_UNKNOWN)
|
||||
, succeed_without_server(true)
|
||||
, log_succeed_without_server(true)
|
||||
, socket_mode(SOCKET_MODE_TCP)
|
||||
, auth(NULL)
|
||||
, backup_request_policy(NULL)
|
||||
, retry_policy(NULL)
|
||||
, ns_filter(NULL)
|
||||
{}
|
||||
|
||||
ChannelSSLOptions* ChannelOptions::mutable_ssl_options() {
|
||||
if (!_ssl_options) {
|
||||
_ssl_options.reset(new ChannelSSLOptions);
|
||||
}
|
||||
return _ssl_options.get();
|
||||
}
|
||||
|
||||
static ChannelSignature ComputeChannelSignature(const ChannelOptions& opt) {
|
||||
if (opt.auth == NULL &&
|
||||
!opt.has_ssl_options() &&
|
||||
opt.client_host.empty() &&
|
||||
opt.device_name.empty() &&
|
||||
opt.connection_group.empty() &&
|
||||
opt.hc_option.health_check_path.empty()) {
|
||||
// Returning zeroized result by default is more intuitive for users.
|
||||
return ChannelSignature();
|
||||
}
|
||||
uint32_t seed = 0;
|
||||
std::string buf;
|
||||
buf.reserve(1024);
|
||||
butil::MurmurHash3_x64_128_Context mm_ctx;
|
||||
do {
|
||||
buf.clear();
|
||||
butil::MurmurHash3_x64_128_Init(&mm_ctx, seed);
|
||||
|
||||
if (!opt.connection_group.empty()) {
|
||||
buf.append("|conng=");
|
||||
buf.append(opt.connection_group);
|
||||
}
|
||||
if (!opt.client_host.empty()) {
|
||||
buf.append("|clih=");
|
||||
buf.append(opt.client_host);
|
||||
}
|
||||
if (!opt.device_name.empty()) {
|
||||
buf.append("|devn=");
|
||||
buf.append(opt.device_name);
|
||||
}
|
||||
if (opt.auth) {
|
||||
buf.append("|auth=");
|
||||
buf.append((char*)&opt.auth, sizeof(opt.auth));
|
||||
}
|
||||
if (!opt.hc_option.health_check_path.empty()) {
|
||||
buf.append("|health_check_path=");
|
||||
buf.append(opt.hc_option.health_check_path);
|
||||
buf.append("|health_check_timeout_ms=");
|
||||
buf.append(std::to_string(opt.hc_option.health_check_timeout_ms));
|
||||
}
|
||||
if (opt.has_ssl_options()) {
|
||||
const ChannelSSLOptions& ssl = opt.ssl_options();
|
||||
buf.push_back('|');
|
||||
buf.append(ssl.ciphers);
|
||||
buf.push_back('|');
|
||||
buf.append(ssl.protocols);
|
||||
buf.push_back('|');
|
||||
buf.append(ssl.sni_name);
|
||||
const VerifyOptions& verify = ssl.verify;
|
||||
buf.push_back('|');
|
||||
buf.append((char*)&verify.verify_depth, sizeof(verify.verify_depth));
|
||||
buf.push_back('|');
|
||||
buf.append(verify.ca_file_path);
|
||||
} else {
|
||||
// All disabled ChannelSSLOptions are the same
|
||||
}
|
||||
if (opt.socket_mode == SOCKET_MODE_RDMA) {
|
||||
buf.append("|rdma");
|
||||
}
|
||||
butil::MurmurHash3_x64_128_Update(&mm_ctx, buf.data(), buf.size());
|
||||
buf.clear();
|
||||
|
||||
if (opt.has_ssl_options()) {
|
||||
const CertInfo& cert = opt.ssl_options().client_cert;
|
||||
if (!cert.certificate.empty()) {
|
||||
// Certificate may be too long (PEM string) to fit into `buf'
|
||||
butil::MurmurHash3_x64_128_Update(
|
||||
&mm_ctx, cert.certificate.data(), cert.certificate.size());
|
||||
butil::MurmurHash3_x64_128_Update(
|
||||
&mm_ctx, cert.private_key.data(), cert.private_key.size());
|
||||
}
|
||||
}
|
||||
// sni_filters has no effect in ChannelSSLOptions
|
||||
ChannelSignature result;
|
||||
butil::MurmurHash3_x64_128_Final(result.data, &mm_ctx);
|
||||
if (result != ChannelSignature()) {
|
||||
// the empty result is reserved for default case and cannot
|
||||
// be used, increment the seed and retry.
|
||||
return result;
|
||||
}
|
||||
++seed;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
Channel::Channel(ProfilerLinker)
|
||||
: _server_id(INVALID_SOCKET_ID)
|
||||
, _serialize_request(NULL)
|
||||
, _pack_request(NULL)
|
||||
, _get_method_name(NULL)
|
||||
, _preferred_index(-1) {
|
||||
}
|
||||
|
||||
Channel::~Channel() {
|
||||
if (_server_id != INVALID_SOCKET_ID) {
|
||||
const ChannelSignature sig = ComputeChannelSignature(_options);
|
||||
SocketMapRemove(SocketMapKey(_server_address, sig));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int Channel::InitChannelOptions(const ChannelOptions* options) {
|
||||
if (options) { // Override default options if user provided one.
|
||||
_options = *options;
|
||||
}
|
||||
const Protocol* protocol = FindProtocol(_options.protocol);
|
||||
if (NULL == protocol || !protocol->support_client()) {
|
||||
LOG(ERROR) << "Channel does not support the protocol";
|
||||
return -1;
|
||||
}
|
||||
if (_options.hc_option.health_check_path.empty()) {
|
||||
_options.hc_option.health_check_path = FLAGS_health_check_path;
|
||||
_options.hc_option.health_check_timeout_ms = FLAGS_health_check_timeout_ms;
|
||||
}
|
||||
auto ret = TransportFactory::ContextInitOrDie(_options.socket_mode, false, &_options);
|
||||
if (ret != 0) {
|
||||
LOG(ERROR) << "Fail to initialize transport context for channel, ret=" << ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
_serialize_request = protocol->serialize_request;
|
||||
_pack_request = protocol->pack_request;
|
||||
_get_method_name = protocol->get_method_name;
|
||||
|
||||
// Check connection_type
|
||||
if (_options.connection_type == CONNECTION_TYPE_UNKNOWN) {
|
||||
// Save has_error which will be overriden in later assignments to
|
||||
// connection_type.
|
||||
const bool has_error = _options.connection_type.has_error();
|
||||
|
||||
if (protocol->supported_connection_type & CONNECTION_TYPE_SINGLE) {
|
||||
_options.connection_type = CONNECTION_TYPE_SINGLE;
|
||||
} else if (protocol->supported_connection_type & CONNECTION_TYPE_POOLED) {
|
||||
_options.connection_type = CONNECTION_TYPE_POOLED;
|
||||
} else {
|
||||
_options.connection_type = CONNECTION_TYPE_SHORT;
|
||||
}
|
||||
if (has_error) {
|
||||
LOG(ERROR) << "Channel=" << this << " chose connection_type="
|
||||
<< _options.connection_type.name() << " for protocol="
|
||||
<< _options.protocol.name();
|
||||
}
|
||||
} else {
|
||||
if (!(_options.connection_type & protocol->supported_connection_type)) {
|
||||
LOG(ERROR) << protocol->name << " does not support connection_type="
|
||||
<< ConnectionTypeToString(_options.connection_type);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
_preferred_index = get_client_side_messenger()->FindProtocolIndex(_options.protocol);
|
||||
if (_preferred_index < 0) {
|
||||
LOG(ERROR) << "Fail to get index for protocol="
|
||||
<< _options.protocol.name();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_options.protocol == PROTOCOL_ESP) {
|
||||
if (_options.auth == NULL) {
|
||||
_options.auth = policy::global_esp_authenticator();
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize connection_group
|
||||
std::string& cg = _options.connection_group;
|
||||
if (!cg.empty() && (::isspace(cg.front()) || ::isspace(cg.back()))) {
|
||||
butil::TrimWhitespace(cg, butil::TRIM_ALL, &cg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Channel::Init(const char* server_addr_and_port,
|
||||
const ChannelOptions* options) {
|
||||
GlobalInitializeOrDie();
|
||||
butil::EndPoint point;
|
||||
const AdaptiveProtocolType& ptype = (options ? options->protocol : _options.protocol);
|
||||
const Protocol* protocol = FindProtocol(ptype);
|
||||
if (protocol == NULL || !protocol->support_client()) {
|
||||
LOG(ERROR) << "Channel does not support the protocol";
|
||||
return -1;
|
||||
}
|
||||
if (protocol->parse_server_address != NULL) {
|
||||
if (!protocol->parse_server_address(&point, server_addr_and_port)) {
|
||||
LOG(ERROR) << "Fail to parse address=`" << server_addr_and_port << '\'';
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (str2endpoint(server_addr_and_port, &point) != 0 &&
|
||||
hostname2endpoint(server_addr_and_port, &point) != 0) {
|
||||
// Many users called the wrong Init(). Print some log to save
|
||||
// our troubleshooting time.
|
||||
if (strstr(server_addr_and_port, "://")) {
|
||||
LOG(ERROR) << "Invalid address=`" << server_addr_and_port
|
||||
<< "'. Use Init(naming_service_name, "
|
||||
"load_balancer_name, options) instead.";
|
||||
} else {
|
||||
LOG(ERROR) << "Invalid address=`" << server_addr_and_port << '\'';
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return InitSingle(point, server_addr_and_port, options);
|
||||
}
|
||||
|
||||
int Channel::Init(const char* server_addr, int port,
|
||||
const ChannelOptions* options) {
|
||||
GlobalInitializeOrDie();
|
||||
butil::EndPoint point;
|
||||
const AdaptiveProtocolType& ptype = (options ? options->protocol : _options.protocol);
|
||||
const Protocol* protocol = FindProtocol(ptype);
|
||||
if (protocol == NULL || !protocol->support_client()) {
|
||||
LOG(ERROR) << "Channel does not support the protocol";
|
||||
return -1;
|
||||
}
|
||||
if (protocol->parse_server_address != NULL) {
|
||||
if (!protocol->parse_server_address(&point, server_addr)) {
|
||||
LOG(ERROR) << "Fail to parse address=`" << server_addr << '\'';
|
||||
return -1;
|
||||
}
|
||||
point.port = port;
|
||||
} else {
|
||||
if (str2endpoint(server_addr, port, &point) != 0 &&
|
||||
hostname2endpoint(server_addr, port, &point) != 0) {
|
||||
LOG(ERROR) << "Invalid address=`" << server_addr << '\'';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return InitSingle(point, server_addr, options, port);
|
||||
}
|
||||
|
||||
static int CreateSocketSSLContext(const ChannelOptions& options,
|
||||
std::shared_ptr<SocketSSLContext>* ssl_ctx) {
|
||||
if (options.has_ssl_options()) {
|
||||
SSL_CTX* raw_ctx = CreateClientSSLContext(options.ssl_options());
|
||||
if (!raw_ctx) {
|
||||
LOG(ERROR) << "Fail to CreateClientSSLContext";
|
||||
return -1;
|
||||
}
|
||||
*ssl_ctx = std::make_shared<SocketSSLContext>();
|
||||
(*ssl_ctx)->raw_ctx = raw_ctx;
|
||||
(*ssl_ctx)->sni_name = options.ssl_options().sni_name;
|
||||
(*ssl_ctx)->alpn_protocols = options.ssl_options().alpn_protocols;
|
||||
} else {
|
||||
(*ssl_ctx) = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Channel::Init(butil::EndPoint server_addr_and_port,
|
||||
const ChannelOptions* options) {
|
||||
return InitSingle(server_addr_and_port, "", options);
|
||||
}
|
||||
|
||||
int Channel::InitSingle(const butil::EndPoint& server_addr_and_port,
|
||||
const char* raw_server_address,
|
||||
const ChannelOptions* options,
|
||||
int raw_port) {
|
||||
GlobalInitializeOrDie();
|
||||
if (InitChannelOptions(options) != 0) {
|
||||
return -1;
|
||||
}
|
||||
int* port_out = raw_port == -1 ? &raw_port: NULL;
|
||||
ParseURL(raw_server_address, &_scheme, &_service_name, port_out);
|
||||
if (raw_port != -1) {
|
||||
_service_name.append(":").append(std::to_string(raw_port));
|
||||
}
|
||||
if (_options.protocol == brpc::PROTOCOL_HTTP && _scheme == "https") {
|
||||
if (_options.mutable_ssl_options()->sni_name.empty()) {
|
||||
_options.mutable_ssl_options()->sni_name = _service_name;
|
||||
}
|
||||
}
|
||||
const int port = server_addr_and_port.port;
|
||||
if (port < 0) {
|
||||
LOG(ERROR) << "Invalid port=" << port;
|
||||
return -1;
|
||||
}
|
||||
butil::EndPoint client_endpoint;
|
||||
if (!_options.client_host.empty() &&
|
||||
butil::str2ip(_options.client_host.c_str(), &client_endpoint.ip) != 0 &&
|
||||
butil::hostname2ip(_options.client_host.c_str(), &client_endpoint.ip) != 0) {
|
||||
LOG(ERROR) << "Invalid client host=`" << _options.client_host << '\'';
|
||||
return -1;
|
||||
}
|
||||
_server_address = server_addr_and_port;
|
||||
const ChannelSignature sig = ComputeChannelSignature(_options);
|
||||
std::shared_ptr<SocketSSLContext> ssl_ctx;
|
||||
if (CreateSocketSSLContext(_options, &ssl_ctx) != 0) {
|
||||
return -1;
|
||||
}
|
||||
SocketOptions opt;
|
||||
opt.local_side = client_endpoint;
|
||||
opt.initial_ssl_ctx = ssl_ctx;
|
||||
opt.socket_mode = _options.socket_mode;
|
||||
opt.hc_option = _options.hc_option;
|
||||
opt.device_name = _options.device_name;
|
||||
if (SocketMapInsert(SocketMapKey(server_addr_and_port, sig),
|
||||
&_server_id, opt) != 0) {
|
||||
LOG(ERROR) << "Fail to insert into SocketMap";
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Channel::Init(const char* ns_url,
|
||||
const char* lb_name,
|
||||
const ChannelOptions* options) {
|
||||
if (lb_name == NULL || *lb_name == '\0') {
|
||||
// Treat ns_url as server_addr_and_port
|
||||
return Init(ns_url, options);
|
||||
}
|
||||
GlobalInitializeOrDie();
|
||||
if (InitChannelOptions(options) != 0) {
|
||||
return -1;
|
||||
}
|
||||
int raw_port = -1;
|
||||
ParseURL(ns_url, &_scheme, &_service_name, &raw_port);
|
||||
if (raw_port != -1) {
|
||||
_service_name.append(":").append(std::to_string(raw_port));
|
||||
}
|
||||
if (_options.protocol == brpc::PROTOCOL_HTTP && _scheme == "https") {
|
||||
if (_options.mutable_ssl_options()->sni_name.empty()) {
|
||||
_options.mutable_ssl_options()->sni_name = _service_name;
|
||||
}
|
||||
}
|
||||
butil::EndPoint client_endpoint;
|
||||
if (!_options.client_host.empty() &&
|
||||
butil::str2ip(_options.client_host.c_str(), &client_endpoint.ip) != 0 &&
|
||||
butil::hostname2ip(_options.client_host.c_str(), &client_endpoint.ip) != 0) {
|
||||
LOG(ERROR) << "Invalid client host=`" << _options.client_host << '\'';
|
||||
return -1;
|
||||
}
|
||||
std::unique_ptr<LoadBalancerWithNaming> lb(new (std::nothrow)
|
||||
LoadBalancerWithNaming);
|
||||
if (NULL == lb) {
|
||||
LOG(FATAL) << "Fail to new LoadBalancerWithNaming";
|
||||
return -1;
|
||||
}
|
||||
GetNamingServiceThreadOptions ns_opt;
|
||||
ns_opt.succeed_without_server = _options.succeed_without_server;
|
||||
ns_opt.log_succeed_without_server = _options.log_succeed_without_server;
|
||||
ns_opt.socket_option.socket_mode = _options.socket_mode;
|
||||
ns_opt.channel_signature = ComputeChannelSignature(_options);
|
||||
ns_opt.socket_option.hc_option = _options.hc_option;
|
||||
ns_opt.socket_option.local_side = client_endpoint;
|
||||
ns_opt.socket_option.device_name = _options.device_name;
|
||||
if (CreateSocketSSLContext(_options,
|
||||
&ns_opt.socket_option.initial_ssl_ctx) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (lb->Init(ns_url, lb_name, _options.ns_filter, &ns_opt) != 0) {
|
||||
LOG(ERROR) << "Fail to initialize LoadBalancerWithNaming";
|
||||
return -1;
|
||||
}
|
||||
_lb.reset(lb.release());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void HandleTimeout(void* arg) {
|
||||
bthread_id_t correlation_id = { (uint64_t)arg };
|
||||
bthread_id_error(correlation_id, ERPCTIMEDOUT);
|
||||
}
|
||||
|
||||
static void HandleBackupRequest(void* arg) {
|
||||
bthread_id_t correlation_id = { (uint64_t)arg };
|
||||
bthread_id_error(correlation_id, EBACKUPREQUEST);
|
||||
}
|
||||
|
||||
void Channel::CallMethod(const google::protobuf::MethodDescriptor* method,
|
||||
google::protobuf::RpcController* controller_base,
|
||||
const google::protobuf::Message* request,
|
||||
google::protobuf::Message* response,
|
||||
google::protobuf::Closure* done) {
|
||||
const int64_t start_send_real_us = butil::gettimeofday_us();
|
||||
Controller* cntl = static_cast<Controller*>(controller_base);
|
||||
cntl->OnRPCBegin(start_send_real_us);
|
||||
// Override max_retry first to reset the range of correlation_id
|
||||
if (cntl->max_retry() == UNSET_MAGIC_NUM) {
|
||||
cntl->set_max_retry(_options.max_retry);
|
||||
}
|
||||
if (cntl->max_retry() < 0) {
|
||||
// this is important because #max_retry decides #versions allocated
|
||||
// in correlation_id. negative max_retry causes undefined behavior.
|
||||
cntl->set_max_retry(0);
|
||||
}
|
||||
// HTTP needs this field to be set before any SetFailed()
|
||||
cntl->_request_protocol = _options.protocol;
|
||||
if (_options.protocol.has_param()) {
|
||||
CHECK(cntl->protocol_param().empty());
|
||||
cntl->protocol_param() = _options.protocol.param();
|
||||
}
|
||||
if (_options.protocol == brpc::PROTOCOL_HTTP && (_scheme == "https" || _scheme == "http")) {
|
||||
URI& uri = cntl->http_request().uri();
|
||||
if (uri.host().empty() && !_service_name.empty()) {
|
||||
uri.SetHostAndPort(_service_name);
|
||||
}
|
||||
}
|
||||
cntl->_preferred_index = _preferred_index;
|
||||
cntl->_retry_policy = _options.retry_policy;
|
||||
if (_options.enable_circuit_breaker) {
|
||||
cntl->add_flag(Controller::FLAGS_ENABLED_CIRCUIT_BREAKER);
|
||||
}
|
||||
const CallId correlation_id = cntl->call_id();
|
||||
const int rc = bthread_id_lock_and_reset_range(
|
||||
correlation_id, NULL, 2 + cntl->max_retry());
|
||||
if (rc != 0) {
|
||||
CHECK_EQ(EINVAL, rc);
|
||||
if (!cntl->FailedInline()) {
|
||||
cntl->SetFailed(EINVAL, "Fail to lock call_id=%" PRId64,
|
||||
correlation_id.value);
|
||||
}
|
||||
LOG_IF(ERROR, cntl->is_used_by_rpc())
|
||||
<< "Controller=" << cntl << " was used by another RPC before. "
|
||||
"Did you forget to Reset() it before reuse?";
|
||||
// Have to run done in-place. If the done runs in another thread,
|
||||
// Join() on this RPC is no-op and probably ends earlier than running
|
||||
// the callback and releases resources used in the callback.
|
||||
// Since this branch is only entered by wrongly-used RPC, the
|
||||
// potentially introduced deadlock(caused by locking RPC and done with
|
||||
// the same non-recursive lock) is acceptable and removable by fixing
|
||||
// user's code.
|
||||
if (done) {
|
||||
done->Run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
cntl->set_used_by_rpc();
|
||||
|
||||
if (cntl->_sender == NULL && IsTraceable(Span::tls_parent().get())) {
|
||||
const int64_t start_send_us = butil::cpuwide_time_us();
|
||||
std::string method_name;
|
||||
if (_get_method_name) {
|
||||
method_name = butil::EnsureString(_get_method_name(method, cntl));
|
||||
} else if (method) {
|
||||
method_name = butil::EnsureString(method->full_name());
|
||||
} else {
|
||||
const static std::string NULL_METHOD_STR = "null-method";
|
||||
method_name = NULL_METHOD_STR;
|
||||
}
|
||||
std::shared_ptr<Span> span = Span::CreateClientSpan(
|
||||
method_name, start_send_real_us - start_send_us);
|
||||
if (span) {
|
||||
ControllerPrivateAccessor accessor(cntl);
|
||||
span->set_log_id(cntl->log_id());
|
||||
span->set_base_cid(correlation_id);
|
||||
span->set_protocol(_options.protocol);
|
||||
span->set_start_send_us(start_send_us);
|
||||
accessor.set_span(span);
|
||||
}
|
||||
}
|
||||
// Override some options if they haven't been set by Controller
|
||||
if (cntl->timeout_ms() == UNSET_MAGIC_NUM) {
|
||||
cntl->set_timeout_ms(_options.timeout_ms);
|
||||
}
|
||||
// Since connection is shared extensively amongst channels and RPC,
|
||||
// overriding connect_timeout_ms does not make sense, just use the
|
||||
// one in ChannelOptions
|
||||
cntl->_connect_timeout_ms = _options.connect_timeout_ms;
|
||||
if (cntl->backup_request_ms() == UNSET_MAGIC_NUM &&
|
||||
NULL == cntl->_backup_request_policy) {
|
||||
cntl->set_backup_request_ms(_options.backup_request_ms);
|
||||
cntl->_backup_request_policy = _options.backup_request_policy;
|
||||
}
|
||||
if (cntl->connection_type() == CONNECTION_TYPE_UNKNOWN) {
|
||||
cntl->set_connection_type(_options.connection_type);
|
||||
}
|
||||
cntl->_response = response;
|
||||
cntl->_done = done;
|
||||
cntl->_pack_request = _pack_request;
|
||||
cntl->_method = method;
|
||||
cntl->_auth = _options.auth;
|
||||
|
||||
if (SingleServer()) {
|
||||
cntl->_single_server_id = _server_id;
|
||||
cntl->_remote_side = _server_address;
|
||||
}
|
||||
|
||||
// Share the lb with controller.
|
||||
cntl->_lb = _lb;
|
||||
|
||||
// Ensure that serialize_request is done before pack_request in all
|
||||
// possible executions, including:
|
||||
// HandleSendFailed => OnVersionedRPCReturned => IssueRPC(pack_request)
|
||||
_serialize_request(&cntl->_request_buf, cntl, request);
|
||||
if (cntl->FailedInline()) {
|
||||
// Handle failures caused by serialize_request, and these error_codes
|
||||
// should be excluded from the retry_policy.
|
||||
return cntl->HandleSendFailed();
|
||||
}
|
||||
if (FLAGS_usercode_in_pthread &&
|
||||
done != NULL &&
|
||||
TooManyUserCode()) {
|
||||
cntl->SetFailed(ELIMIT, "Too many user code to run when "
|
||||
"-usercode_in_pthread is on");
|
||||
return cntl->HandleSendFailed();
|
||||
}
|
||||
|
||||
if (!cntl->_request_streams.empty()) {
|
||||
// Currently we cannot handle retry and backup request correctly
|
||||
cntl->set_max_retry(0);
|
||||
cntl->set_backup_request_ms(-1);
|
||||
cntl->_backup_request_policy = NULL;
|
||||
}
|
||||
|
||||
if (cntl->backup_request_ms() >= 0 &&
|
||||
(cntl->backup_request_ms() < cntl->timeout_ms() ||
|
||||
cntl->timeout_ms() < 0)) {
|
||||
// Setup timer for backup request. When it occurs, we'll setup a
|
||||
// timer of timeout_ms before sending backup request.
|
||||
|
||||
// _deadline_us is for truncating _connect_timeout_ms and resetting
|
||||
// timer when EBACKUPREQUEST occurs.
|
||||
if (cntl->timeout_ms() < 0) {
|
||||
cntl->_deadline_us = -1;
|
||||
} else {
|
||||
cntl->_deadline_us = cntl->timeout_ms() * 1000L + start_send_real_us;
|
||||
}
|
||||
const int rc = bthread_timer_add(
|
||||
&cntl->_timeout_id,
|
||||
butil::microseconds_to_timespec(
|
||||
cntl->backup_request_ms() * 1000L + start_send_real_us),
|
||||
HandleBackupRequest, (void*)correlation_id.value);
|
||||
if (BAIDU_UNLIKELY(rc != 0)) {
|
||||
cntl->SetFailed(rc, "Fail to add timer for backup request");
|
||||
return cntl->HandleSendFailed();
|
||||
}
|
||||
} else if (cntl->timeout_ms() >= 0) {
|
||||
// Setup timer for RPC timetout
|
||||
|
||||
// _deadline_us is for truncating _connect_timeout_ms
|
||||
cntl->_deadline_us = cntl->timeout_ms() * 1000L + start_send_real_us;
|
||||
const int rc = bthread_timer_add(
|
||||
&cntl->_timeout_id,
|
||||
butil::microseconds_to_timespec(cntl->_deadline_us),
|
||||
HandleTimeout, (void*)correlation_id.value);
|
||||
if (BAIDU_UNLIKELY(rc != 0)) {
|
||||
cntl->SetFailed(rc, "Fail to add timer for timeout");
|
||||
return cntl->HandleSendFailed();
|
||||
}
|
||||
} else {
|
||||
cntl->_deadline_us = -1;
|
||||
}
|
||||
|
||||
cntl->IssueRPC(start_send_real_us);
|
||||
if (done == NULL) {
|
||||
// MUST wait for response when sending synchronous RPC. It will
|
||||
// be woken up by callback when RPC finishes (succeeds or still
|
||||
// fails after retry)
|
||||
Join(correlation_id);
|
||||
cntl->SubmitSpan();
|
||||
cntl->OnRPCEnd(butil::gettimeofday_us());
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::Describe(std::ostream& os, const DescribeOptions& opt) const {
|
||||
os << "Channel[";
|
||||
if (SingleServer()) {
|
||||
os << _server_address;
|
||||
} else {
|
||||
_lb->Describe(os, opt);
|
||||
}
|
||||
os << "]";
|
||||
}
|
||||
|
||||
int Channel::Weight() {
|
||||
return (_lb ? _lb->Weight() : 0);
|
||||
}
|
||||
|
||||
int Channel::CheckHealth() {
|
||||
if (_lb == NULL) {
|
||||
SocketUniquePtr ptr;
|
||||
if (Socket::Address(_server_id, &ptr) == 0 && ptr->IsAvailable()) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
} else {
|
||||
SocketUniquePtr tmp_sock;
|
||||
LoadBalancer::SelectIn sel_in = { 0, false, true, 0, NULL };
|
||||
LoadBalancer::SelectOut sel_out(&tmp_sock);
|
||||
return _lb->SelectServer(sel_in, &sel_out);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,277 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_CHANNEL_H
|
||||
#define BRPC_CHANNEL_H
|
||||
|
||||
// To brpc developers: This is a header included by user, don't depend
|
||||
// on internal structures, use opaque pointers instead.
|
||||
|
||||
#include <ostream> // std::ostream
|
||||
#include "bthread/errno.h" // Redefine errno
|
||||
#include "butil/intrusive_ptr.hpp" // butil::intrusive_ptr
|
||||
#include "butil/ptr_container.h"
|
||||
#include "brpc/ssl_options.h" // ChannelSSLOptions
|
||||
#include "brpc/channel_base.h" // ChannelBase
|
||||
#include "brpc/adaptive_protocol_type.h" // AdaptiveProtocolType
|
||||
#include "brpc/adaptive_connection_type.h" // AdaptiveConnectionType
|
||||
#include "brpc/socket_id.h" // SocketId
|
||||
#include "brpc/controller.h" // brpc::Controller
|
||||
#include "brpc/details/profiler_linker.h"
|
||||
#include "brpc/retry_policy.h"
|
||||
#include "brpc/backup_request_policy.h"
|
||||
#include "brpc/naming_service_filter.h"
|
||||
#include "brpc/health_check_option.h"
|
||||
#include "brpc/socket_mode.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
struct ChannelOptions {
|
||||
// Constructed with default options.
|
||||
ChannelOptions();
|
||||
|
||||
// Issue error when a connection is not established after so many
|
||||
// milliseconds. -1 means wait indefinitely.
|
||||
// Default: 200 (milliseconds)
|
||||
// Maximum: 0x7fffffff (roughly 30 days)
|
||||
int32_t connect_timeout_ms;
|
||||
|
||||
// Max duration of RPC over this Channel. -1 means wait indefinitely.
|
||||
// Overridable by Controller.set_timeout_ms().
|
||||
// Default: 500 (milliseconds)
|
||||
// Maximum: 0x7fffffff (roughly 30 days)
|
||||
int32_t timeout_ms;
|
||||
|
||||
// Send another request if RPC does not finish after so many milliseconds.
|
||||
// Overridable by Controller.set_backup_request_ms() or
|
||||
// Controller.set_backup_request_policy().
|
||||
// The request will be sent to a different server by best effort.
|
||||
// If timeout_ms is set and backup_request_ms >= timeout_ms, backup request
|
||||
// will never be sent.
|
||||
// backup request does NOT imply server-side cancellation.
|
||||
// Default: -1 (disabled)
|
||||
// Maximum: 0x7fffffff (roughly 30 days)
|
||||
int32_t backup_request_ms;
|
||||
|
||||
// Retry limit for RPC over this Channel. <=0 means no retry.
|
||||
// Overridable by Controller.set_max_retry().
|
||||
// Default: 3
|
||||
// Maximum: INT_MAX
|
||||
int max_retry;
|
||||
|
||||
// When the error rate of a server node is too high, isolate the node.
|
||||
// Note that this isolation is GLOBAL, the node will become unavailable
|
||||
// for all channels running in this process during the isolation.
|
||||
// Default: false
|
||||
bool enable_circuit_breaker;
|
||||
|
||||
// Serialization protocol, defined in src/brpc/options.proto
|
||||
// NOTE: You can assign name of the protocol to this field as well, for
|
||||
// Example: options.protocol = "baidu_std";
|
||||
AdaptiveProtocolType protocol;
|
||||
|
||||
// Type of connection to server. If unset, use the default connection type
|
||||
// of the protocol.
|
||||
// NOTE: You can assign name of the type to this field as well, for
|
||||
// Example: options.connection_type = "single";
|
||||
// Possible values: "single", "pooled", "short".
|
||||
AdaptiveConnectionType connection_type;
|
||||
|
||||
// Channel.Init() succeeds even if there's no server in the NamingService.
|
||||
// E.g. the BNS directory is empty. All RPC over the channel will fail before
|
||||
// new nodes being added to the NamingService.
|
||||
// Default: true (false before r32470)
|
||||
bool succeed_without_server;
|
||||
// Print a log when above situation happens.
|
||||
// Default: true.
|
||||
bool log_succeed_without_server;
|
||||
|
||||
// SSL related options. Refer to `ChannelSSLOptions' for details
|
||||
bool has_ssl_options() const { return _ssl_options != NULL; }
|
||||
const ChannelSSLOptions& ssl_options() const { return *_ssl_options; }
|
||||
ChannelSSLOptions* mutable_ssl_options();
|
||||
|
||||
// Let this channel Choose to use a certain socket: 0 SOCKET_MODE_TCP, 1 SOCKET_MODE_RDMA.
|
||||
// Default: SOCKET_MODE_TCP
|
||||
SocketMode socket_mode;
|
||||
|
||||
// Turn on authentication for this channel if `auth' is not NULL.
|
||||
// Note `auth' will not be deleted by channel and must remain valid when
|
||||
// the channel is being used.
|
||||
// Default: NULL
|
||||
const Authenticator* auth;
|
||||
|
||||
// Customize the backup request time and whether to send backup request.
|
||||
// Priority: `backup_request_policy' > `backup_request_ms'.
|
||||
// Overridable per-RPC by Controller.set_backup_request_ms() or
|
||||
// Controller.set_backup_request_policy().
|
||||
// This object is NOT owned by channel and should remain valid during
|
||||
// channel's lifetime.
|
||||
// Default: NULL
|
||||
BackupRequestPolicy* backup_request_policy;
|
||||
|
||||
// Customize the error code that should be retried. The interface is
|
||||
// defined in src/brpc/retry_policy.h
|
||||
// This object is NOT owned by channel and should remain valid when
|
||||
// channel is used.
|
||||
// Default: NULL
|
||||
const RetryPolicy* retry_policy;
|
||||
|
||||
// Filter ServerNodes (i.e. based on `tag' field of `ServerNode')
|
||||
// which are generated by NamingService. The interface is defined
|
||||
// in src/brpc/naming_service_filter.h
|
||||
// This object is NOT owned by channel and should remain valid when
|
||||
// channel is used.
|
||||
// Default: NULL
|
||||
const NamingServiceFilter* ns_filter;
|
||||
|
||||
// Channels with same connection_group share connections.
|
||||
// In other words, set to a different value to stop sharing connections.
|
||||
// Case-sensitive, leading and trailing spaces are ignored.
|
||||
// Default: ""
|
||||
std::string connection_group;
|
||||
|
||||
// Set the health check param according to the channel granularity.
|
||||
// Its priority is higher than FLAGS_health_check_path and FLAGS_health_check_timeout_ms.
|
||||
// When it is not set, FLAGS_health_check_path and FLAGS_health_check_timeout_ms will take effect.
|
||||
HealthCheckOption hc_option;
|
||||
|
||||
// IP address or host name of the client.
|
||||
// if the client_host is "", the client IP address is determined by the OS.
|
||||
// Default: ""
|
||||
std::string client_host;
|
||||
|
||||
// The device name of the client's network adapter.
|
||||
// if the device_name is "", the flow control is determined by the OS.
|
||||
// Default: ""
|
||||
std::string device_name;
|
||||
private:
|
||||
// SSLOptions is large and not often used, allocate it on heap to
|
||||
// prevent ChannelOptions from being bloated in most cases.
|
||||
butil::PtrContainer<ChannelSSLOptions> _ssl_options;
|
||||
};
|
||||
|
||||
// A Channel represents a communication line to one server or multiple servers
|
||||
// which can be used to call that Server's services. Servers may be running
|
||||
// on another machines. Normally, you should not call a Channel directly, but
|
||||
// instead construct a stub Service wrapping it.
|
||||
// Example:
|
||||
// brpc::Channel channel;
|
||||
// channel.Init("bns://rdev.matrix.all", "rr", NULL/*default options*/);
|
||||
// MyService_Stub stub(&channel);
|
||||
// stub.MyMethod(&controller, &request, &response, NULL);
|
||||
class Channel : public ChannelBase {
|
||||
friend class Controller;
|
||||
friend class SelectiveChannel;
|
||||
public:
|
||||
Channel(ProfilerLinker = ProfilerLinker());
|
||||
virtual ~Channel();
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Channel);
|
||||
|
||||
// Connect this channel to a single server whose address is given by the
|
||||
// first parameter. Use default options if `options' is NULL.
|
||||
int Init(butil::EndPoint server_addr_and_port, const ChannelOptions* options);
|
||||
int Init(const char* server_addr_and_port, const ChannelOptions* options);
|
||||
int Init(const char* server_addr, int port, const ChannelOptions* options);
|
||||
|
||||
// Connect this channel to a group of servers whose addresses can be
|
||||
// accessed via `naming_service_url' according to its protocol. Use the
|
||||
// method specified by `load_balancer_name' to distribute traffic to
|
||||
// servers. Use default options if `options' is NULL.
|
||||
// Supported naming service("protocol://service_name"):
|
||||
// bns://<node-name> # Baidu Naming Service
|
||||
// file://<file-path> # load addresses from the file
|
||||
// list://addr1,addr2,... # use the addresses separated by comma
|
||||
// http://<url> # Domain Naming Service, aka DNS.
|
||||
// Supported load balancer:
|
||||
// rr # round robin, choose next server
|
||||
// random # randomly choose a server
|
||||
// wr # weighted random
|
||||
// wrr # weighted round robin
|
||||
// la # locality aware
|
||||
// c_murmurhash/c_md5 # consistent hashing with murmurhash3/md5
|
||||
// "" or NULL # treat `naming_service_url' as `server_addr_and_port'
|
||||
// # Init(xxx, "", options) and Init(xxx, NULL, options)
|
||||
// # are exactly same with Init(xxx, options)
|
||||
int Init(const char* naming_service_url,
|
||||
const char* load_balancer_name,
|
||||
const ChannelOptions* options);
|
||||
|
||||
// Call `method' of the remote service with `request' as input, and
|
||||
// `response' as output. `controller' contains options and extra data.
|
||||
// If `done' is not NULL, this method returns after request was sent
|
||||
// and `done->Run()' will be called when the call finishes, otherwise
|
||||
// caller blocks until the call finishes.
|
||||
void CallMethod(const google::protobuf::MethodDescriptor* method,
|
||||
google::protobuf::RpcController* controller,
|
||||
const google::protobuf::Message* request,
|
||||
google::protobuf::Message* response,
|
||||
google::protobuf::Closure* done);
|
||||
|
||||
// Get current options.
|
||||
const ChannelOptions& options() const { return _options; }
|
||||
|
||||
void Describe(std::ostream&, const DescribeOptions&) const;
|
||||
|
||||
// Sum of weights of servers that this channel connects to.
|
||||
int Weight();
|
||||
|
||||
int CheckHealth();
|
||||
|
||||
protected:
|
||||
bool SingleServer() const { return _lb.get() == NULL; }
|
||||
|
||||
// Pick a server using `lb' and then send RPC. Wait for response when
|
||||
// sending synchronous RPC.
|
||||
// NOTE: DO NOT directly use `controller' after this call when
|
||||
// sending asynchronous RPC (controller->_done != NULL) since
|
||||
// user callback `done' could be called when it returns and
|
||||
// therefore destroy the `controller' inside `done'
|
||||
static void CallMethodImpl(Controller* controller, SharedLoadBalancer* lb);
|
||||
|
||||
int InitChannelOptions(const ChannelOptions* options);
|
||||
int InitSingle(const butil::EndPoint& server_addr_and_port,
|
||||
const char* raw_server_address,
|
||||
const ChannelOptions* options,
|
||||
int raw_port = -1);
|
||||
|
||||
std::string _service_name;
|
||||
std::string _scheme;
|
||||
butil::EndPoint _server_address;
|
||||
SocketId _server_id;
|
||||
Protocol::SerializeRequest _serialize_request;
|
||||
Protocol::PackRequest _pack_request;
|
||||
Protocol::GetMethodName _get_method_name;
|
||||
// This will be shared between channel and controllers that
|
||||
// are in the middle of RPC procedure using this channel.
|
||||
// It will be destroyed after channel's destruction and all
|
||||
// the RPC above has finished
|
||||
butil::intrusive_ptr<SharedLoadBalancer> _lb;
|
||||
ChannelOptions _options;
|
||||
int _preferred_index;
|
||||
};
|
||||
|
||||
enum ChannelOwnership {
|
||||
OWNS_CHANNEL,
|
||||
DOESNT_OWN_CHANNEL
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif // BRPC_CHANNEL_H
|
||||
@@ -0,0 +1,49 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_CHANNEL_BASE_H
|
||||
#define BRPC_CHANNEL_BASE_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ostream>
|
||||
#include "butil/logging.h"
|
||||
#include <google/protobuf/service.h> // google::protobuf::RpcChannel
|
||||
#include "brpc/describable.h"
|
||||
|
||||
// To brpc developers: This is a header included by user, don't depend
|
||||
// on internal structures, use opaque pointers instead.
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Base of all brpc channels.
|
||||
class ChannelBase : public google::protobuf::RpcChannel/*non-copyable*/,
|
||||
public Describable {
|
||||
public:
|
||||
virtual int Weight() {
|
||||
CHECK(false) << "Not implemented";
|
||||
abort();
|
||||
};
|
||||
|
||||
virtual int CheckHealth() = 0;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_CHANNEL_BASE_H
|
||||
@@ -0,0 +1,100 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#include "brpc/checksum.h"
|
||||
|
||||
#include "brpc/protocol.h"
|
||||
#include "butil/logging.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
static const int MAX_HANDLER_SIZE = 1024;
|
||||
static ChecksumHandler s_handler_map[MAX_HANDLER_SIZE] = {{NULL, NULL, NULL}};
|
||||
|
||||
int RegisterChecksumHandler(ChecksumType type, ChecksumHandler handler) {
|
||||
if (NULL == handler.Compute) {
|
||||
LOG(FATAL) << "Invalid parameter: handler function is NULL";
|
||||
return -1;
|
||||
}
|
||||
int index = type;
|
||||
if (index < 0 || index >= MAX_HANDLER_SIZE) {
|
||||
LOG(FATAL) << "ChecksumType=" << type << " is out of range";
|
||||
return -1;
|
||||
}
|
||||
if (s_handler_map[index].Compute != NULL) {
|
||||
LOG(FATAL) << "ChecksumType=" << type << " was registered";
|
||||
return -1;
|
||||
}
|
||||
s_handler_map[index] = handler;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find ChecksumHandler by type.
|
||||
// Returns NULL if not found
|
||||
inline const ChecksumHandler* FindChecksumHandler(ChecksumType type) {
|
||||
int index = type;
|
||||
if (index < 0 || index >= MAX_HANDLER_SIZE) {
|
||||
LOG(ERROR) << "ChecksumType=" << type << " is out of range";
|
||||
return NULL;
|
||||
}
|
||||
if (NULL == s_handler_map[index].Compute) {
|
||||
return NULL;
|
||||
}
|
||||
return &s_handler_map[index];
|
||||
}
|
||||
|
||||
const char* ChecksumTypeToCStr(ChecksumType type) {
|
||||
if (type == CHECKSUM_TYPE_NONE) {
|
||||
return "none";
|
||||
}
|
||||
const ChecksumHandler* handler = FindChecksumHandler(type);
|
||||
return (handler != NULL ? handler->name : "unknown");
|
||||
}
|
||||
|
||||
void ListChecksumHandler(std::vector<ChecksumHandler>* vec) {
|
||||
vec->clear();
|
||||
for (int i = 0; i < MAX_HANDLER_SIZE; ++i) {
|
||||
if (s_handler_map[i].Compute != NULL) {
|
||||
vec->push_back(s_handler_map[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute `data' checksum
|
||||
void ComputeDataChecksum(const ChecksumIn& in, ChecksumType checksum_type) {
|
||||
if (checksum_type == CHECKSUM_TYPE_NONE) {
|
||||
return;
|
||||
}
|
||||
const ChecksumHandler* handler = FindChecksumHandler(checksum_type);
|
||||
if (NULL != handler) {
|
||||
handler->Compute(in);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify `data' checksum Returns true on success, false otherwise
|
||||
bool VerifyDataChecksum(const ChecksumIn& in, ChecksumType checksum_type) {
|
||||
if (checksum_type == CHECKSUM_TYPE_NONE) {
|
||||
return true;
|
||||
}
|
||||
const ChecksumHandler* handler = FindChecksumHandler(checksum_type);
|
||||
if (NULL != handler) {
|
||||
return handler->Verify(in);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,63 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_CHECKSUM_H
|
||||
#define BRPC_CHECKSUM_H
|
||||
|
||||
#include "brpc/controller.h"
|
||||
#include "brpc/options.pb.h" // ChecksumType
|
||||
#include "butil/iobuf.h" // butil::IOBuf
|
||||
|
||||
namespace brpc {
|
||||
|
||||
struct ChecksumIn {
|
||||
const butil::IOBuf* buf;
|
||||
Controller* cntl;
|
||||
};
|
||||
|
||||
struct ChecksumHandler {
|
||||
// checksum `buf'.
|
||||
// Returns checksum value
|
||||
void (*Compute)(const ChecksumIn& in);
|
||||
|
||||
// verify buf checksum
|
||||
// Rerturn true on success, false otherwise
|
||||
bool (*Verify)(const ChecksumIn& in);
|
||||
|
||||
// Name of the checksum algorithm, must be string constant.
|
||||
const char* name;
|
||||
};
|
||||
|
||||
// [NOT thread-safe] Register `handler' using key=`type'
|
||||
// Returns 0 on success, -1 otherwise
|
||||
int RegisterChecksumHandler(ChecksumType type, ChecksumHandler handler);
|
||||
|
||||
// Returns the `name' of the checksumType if registered
|
||||
const char* ChecksumTypeToCStr(ChecksumType type);
|
||||
|
||||
// Put all registered handlers into `vec'.
|
||||
void ListChecksumHandler(std::vector<ChecksumHandler>* vec);
|
||||
|
||||
// Compute `data' checksum and set to controller
|
||||
void ComputeDataChecksum(const ChecksumIn& in, ChecksumType checksum_type);
|
||||
|
||||
// Verify `data' checksum Returns true on success, false otherwise
|
||||
bool VerifyDataChecksum(const ChecksumIn& in, ChecksumType checksum_type);
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif // BRPC_CHECKSUM_H
|
||||
@@ -0,0 +1,256 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#include "brpc/circuit_breaker.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "brpc/errno.pb.h"
|
||||
#include "brpc/reloadable_flags.h"
|
||||
#include "butil/time.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
DEFINE_int32(circuit_breaker_short_window_size, 1500,
|
||||
"Short window sample size.");
|
||||
DEFINE_int32(circuit_breaker_long_window_size, 3000,
|
||||
"Long window sample size.");
|
||||
DEFINE_int32(circuit_breaker_short_window_error_percent, 10,
|
||||
"The maximum error rate allowed by the short window, ranging from 0-99.");
|
||||
DEFINE_int32(circuit_breaker_long_window_error_percent, 5,
|
||||
"The maximum error rate allowed by the long window, ranging from 0-99.");
|
||||
DEFINE_int32(circuit_breaker_min_error_cost_us, 500,
|
||||
"The minimum error_cost, when the ema of error cost is less than this "
|
||||
"value, it will be set to zero.");
|
||||
DEFINE_int32(circuit_breaker_max_failed_latency_mutiple, 2,
|
||||
"The maximum multiple of the latency of the failed request relative to "
|
||||
"the average latency of the success requests.");
|
||||
DEFINE_int32(circuit_breaker_min_isolation_duration_ms, 100,
|
||||
"Minimum isolation duration in milliseconds");
|
||||
DEFINE_int32(circuit_breaker_max_isolation_duration_ms, 30000,
|
||||
"Maximum isolation duration in milliseconds");
|
||||
DEFINE_double(circuit_breaker_epsilon_value, 0.02,
|
||||
"ema_alpha = 1 - std::pow(epsilon, 1.0 / window_size)");
|
||||
DEFINE_int32(circuit_breaker_half_open_window_size, 0,
|
||||
"The limited number of requests allowed to pass through by the half-open "
|
||||
"window. Only if all of them are successful, the circuit breaker will "
|
||||
"go to the closed state. Otherwise, it goes back to the open state. "
|
||||
"Values == 0 disables this feature");
|
||||
BRPC_VALIDATE_GFLAG(circuit_breaker_half_open_window_size, NonNegativeInteger);
|
||||
|
||||
namespace {
|
||||
// EPSILON is used to generate the smoothing coefficient when calculating EMA.
|
||||
// The larger the EPSILON, the larger the smoothing coefficient, which means
|
||||
// that the proportion of early data is larger.
|
||||
// smooth = pow(EPSILON, 1 / window_size),
|
||||
// eg: when window_size = 100,
|
||||
// EPSILON = 0.1, smooth = 0.9772
|
||||
// EPSILON = 0.3, smooth = 0.9880
|
||||
// when window_size = 1000,
|
||||
// EPSILON = 0.1, smooth = 0.9977
|
||||
// EPSILON = 0.3, smooth = 0.9987
|
||||
|
||||
#define EPSILON (FLAGS_circuit_breaker_epsilon_value)
|
||||
|
||||
} // namespace
|
||||
|
||||
CircuitBreaker::EmaErrorRecorder::EmaErrorRecorder(int window_size,
|
||||
int max_error_percent)
|
||||
: _window_size(window_size)
|
||||
, _max_error_percent(max_error_percent)
|
||||
, _smooth(std::pow(EPSILON, 1.0/window_size))
|
||||
, _sample_count_when_initializing(0)
|
||||
, _error_count_when_initializing(0)
|
||||
, _ema_error_cost(0)
|
||||
, _ema_latency(0) {
|
||||
}
|
||||
|
||||
bool CircuitBreaker::EmaErrorRecorder::OnCallEnd(int error_code,
|
||||
int64_t latency) {
|
||||
int64_t ema_latency = 0;
|
||||
bool healthy = false;
|
||||
if (error_code == 0) {
|
||||
ema_latency = UpdateLatency(latency);
|
||||
healthy = UpdateErrorCost(0, ema_latency);
|
||||
} else {
|
||||
ema_latency = _ema_latency.load(butil::memory_order_relaxed);
|
||||
healthy = UpdateErrorCost(latency, ema_latency);
|
||||
}
|
||||
|
||||
// When the window is initializing, use error_rate to determine
|
||||
// if it needs to be isolated.
|
||||
if (_sample_count_when_initializing.load(butil::memory_order_relaxed) < _window_size &&
|
||||
_sample_count_when_initializing.fetch_add(1, butil::memory_order_relaxed) < _window_size) {
|
||||
if (error_code != 0) {
|
||||
const int32_t error_count =
|
||||
_error_count_when_initializing.fetch_add(1, butil::memory_order_relaxed);
|
||||
return error_count < _window_size * _max_error_percent / 100;
|
||||
}
|
||||
// Because once OnCallEnd returned false, the node will be ioslated soon,
|
||||
// so when error_code=0, we no longer check the error count.
|
||||
return true;
|
||||
}
|
||||
|
||||
return healthy;
|
||||
}
|
||||
|
||||
void CircuitBreaker::EmaErrorRecorder::Reset() {
|
||||
if (_sample_count_when_initializing.load(butil::memory_order_relaxed) < _window_size) {
|
||||
_sample_count_when_initializing.store(0, butil::memory_order_relaxed);
|
||||
_error_count_when_initializing.store(0, butil::memory_order_relaxed);
|
||||
_ema_latency.store(0, butil::memory_order_relaxed);
|
||||
}
|
||||
_ema_error_cost.store(0, butil::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int64_t CircuitBreaker::EmaErrorRecorder::UpdateLatency(int64_t latency) {
|
||||
int64_t ema_latency = _ema_latency.load(butil::memory_order_relaxed);
|
||||
do {
|
||||
int64_t next_ema_latency = 0;
|
||||
if (0 == ema_latency) {
|
||||
next_ema_latency = latency;
|
||||
} else {
|
||||
next_ema_latency = ema_latency * _smooth + latency * (1 - _smooth);
|
||||
}
|
||||
if (_ema_latency.compare_exchange_weak(ema_latency, next_ema_latency)) {
|
||||
return next_ema_latency;
|
||||
}
|
||||
} while(true);
|
||||
}
|
||||
|
||||
bool CircuitBreaker::EmaErrorRecorder::UpdateErrorCost(int64_t error_cost,
|
||||
int64_t ema_latency) {
|
||||
const int max_mutiple = FLAGS_circuit_breaker_max_failed_latency_mutiple;
|
||||
if (ema_latency != 0) {
|
||||
error_cost = std::min(ema_latency * max_mutiple, error_cost);
|
||||
}
|
||||
// Errorous response
|
||||
if (error_cost != 0) {
|
||||
int64_t ema_error_cost =
|
||||
_ema_error_cost.fetch_add(error_cost, butil::memory_order_relaxed);
|
||||
ema_error_cost += error_cost;
|
||||
const int64_t max_error_cost =
|
||||
ema_latency * _window_size * (_max_error_percent / 100.0) * (1.0 + EPSILON);
|
||||
return ema_error_cost <= max_error_cost;
|
||||
}
|
||||
|
||||
// Ordinary response
|
||||
int64_t ema_error_cost = _ema_error_cost.load(butil::memory_order_relaxed);
|
||||
do {
|
||||
if (ema_error_cost == 0) {
|
||||
break;
|
||||
} else if (ema_error_cost < FLAGS_circuit_breaker_min_error_cost_us) {
|
||||
if (_ema_error_cost.compare_exchange_weak(
|
||||
ema_error_cost, 0, butil::memory_order_relaxed)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
int64_t next_ema_error_cost = ema_error_cost * _smooth;
|
||||
if (_ema_error_cost.compare_exchange_weak(
|
||||
ema_error_cost, next_ema_error_cost)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
return true;
|
||||
}
|
||||
|
||||
CircuitBreaker::CircuitBreaker()
|
||||
: _long_window(FLAGS_circuit_breaker_long_window_size,
|
||||
FLAGS_circuit_breaker_long_window_error_percent)
|
||||
, _short_window(FLAGS_circuit_breaker_short_window_size,
|
||||
FLAGS_circuit_breaker_short_window_error_percent)
|
||||
, _last_reset_time_ms(0)
|
||||
, _isolation_duration_ms(FLAGS_circuit_breaker_min_isolation_duration_ms)
|
||||
, _isolated_times(0)
|
||||
, _broken(false)
|
||||
, _half_open(false)
|
||||
, _half_open_success_count(0) {
|
||||
}
|
||||
|
||||
bool CircuitBreaker::OnCallEnd(int error_code, int64_t latency) {
|
||||
// If the server has reached its maximum concurrency, it will return
|
||||
// ELIMIT directly when a new request arrives. This usually means that
|
||||
// the entire downstream cluster is overloaded. If we isolate nodes at
|
||||
// this time, may increase the pressure on downstream. On the other hand,
|
||||
// since the latency corresponding to ELIMIT is usually very small, we
|
||||
// cannot handle it as a successful request. Here we simply ignore the requests
|
||||
// that returned ELIMIT.
|
||||
if (error_code == ELIMIT) {
|
||||
return true;
|
||||
}
|
||||
if (_broken.load(butil::memory_order_relaxed)) {
|
||||
return false;
|
||||
}
|
||||
if (FLAGS_circuit_breaker_half_open_window_size > 0
|
||||
&& _half_open.load(butil::memory_order_relaxed)) {
|
||||
if (error_code != 0) {
|
||||
MarkAsBroken();
|
||||
return false;
|
||||
}
|
||||
if (_half_open_success_count.fetch_add(1, butil::memory_order_relaxed)
|
||||
+ 1 == FLAGS_circuit_breaker_half_open_window_size) {
|
||||
_half_open.store(false, butil::memory_order_relaxed);
|
||||
_half_open_success_count.store(0, butil::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
if (_long_window.OnCallEnd(error_code, latency) &&
|
||||
_short_window.OnCallEnd(error_code, latency)) {
|
||||
return true;
|
||||
}
|
||||
MarkAsBroken();
|
||||
return false;
|
||||
}
|
||||
|
||||
void CircuitBreaker::Reset() {
|
||||
_long_window.Reset();
|
||||
_short_window.Reset();
|
||||
_last_reset_time_ms = butil::cpuwide_time_ms();
|
||||
_broken.store(false, butil::memory_order_release);
|
||||
if (FLAGS_circuit_breaker_half_open_window_size > 0) {
|
||||
_half_open.store(true, butil::memory_order_relaxed);
|
||||
_half_open_success_count.store(0, butil::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
void CircuitBreaker::MarkAsBroken() {
|
||||
if (!_broken.exchange(true, butil::memory_order_acquire)) {
|
||||
_isolated_times.fetch_add(1, butil::memory_order_relaxed);
|
||||
UpdateIsolationDuration();
|
||||
}
|
||||
}
|
||||
|
||||
void CircuitBreaker::UpdateIsolationDuration() {
|
||||
int64_t now_time_ms = butil::cpuwide_time_ms();
|
||||
int isolation_duration_ms = _isolation_duration_ms.load(butil::memory_order_relaxed);
|
||||
const int max_isolation_duration_ms =
|
||||
FLAGS_circuit_breaker_max_isolation_duration_ms;
|
||||
const int min_isolation_duration_ms =
|
||||
FLAGS_circuit_breaker_min_isolation_duration_ms;
|
||||
if (now_time_ms - _last_reset_time_ms < max_isolation_duration_ms) {
|
||||
isolation_duration_ms =
|
||||
std::min(isolation_duration_ms * 2, max_isolation_duration_ms);
|
||||
} else {
|
||||
isolation_duration_ms = min_isolation_duration_ms;
|
||||
}
|
||||
_isolation_duration_ms.store(isolation_duration_ms, butil::memory_order_relaxed);
|
||||
}
|
||||
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,96 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_CIRCUIT_BREAKER_H
|
||||
#define BRPC_CIRCUIT_BREAKER_H
|
||||
|
||||
#include "butil/atomicops.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class CircuitBreaker {
|
||||
public:
|
||||
CircuitBreaker();
|
||||
|
||||
~CircuitBreaker() {}
|
||||
|
||||
// Sampling the current rpc. Returns false if a node needs to
|
||||
// be isolated. Otherwise return true.
|
||||
// error_code: Error_code of this call, 0 means success.
|
||||
// latency: Time cost of this call.
|
||||
// Note: Once OnCallEnd() determined that a node needs to be isolated,
|
||||
// it will always return false until you call Reset(). Usually Reset()
|
||||
// will be called in the health check thread.
|
||||
bool OnCallEnd(int error_code, int64_t latency);
|
||||
|
||||
// Reset CircuitBreaker and clear history data. will erase the historical
|
||||
// data and start sampling again. Before you call this method, you need to
|
||||
// ensure that no one else is accessing CircuitBreaker.
|
||||
void Reset();
|
||||
|
||||
// Mark the Socket as broken. Call this method when you want to isolate a
|
||||
// node in advance. When this method is called multiple times in succession,
|
||||
// only the first call will take effect.
|
||||
void MarkAsBroken();
|
||||
|
||||
// Number of times marked as broken
|
||||
int isolated_times() const {
|
||||
return _isolated_times.load(butil::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// The duration that should be isolated when the socket fails in milliseconds.
|
||||
// The higher the frequency of socket errors, the longer the duration.
|
||||
int isolation_duration_ms() const {
|
||||
return _isolation_duration_ms.load(butil::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateIsolationDuration();
|
||||
|
||||
class EmaErrorRecorder {
|
||||
public:
|
||||
EmaErrorRecorder(int windows_size, int max_error_percent);
|
||||
bool OnCallEnd(int error_code, int64_t latency);
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
int64_t UpdateLatency(int64_t latency);
|
||||
bool UpdateErrorCost(int64_t latency, int64_t ema_latency);
|
||||
|
||||
const int _window_size;
|
||||
const int _max_error_percent;
|
||||
const double _smooth;
|
||||
|
||||
butil::atomic<int32_t> _sample_count_when_initializing;
|
||||
butil::atomic<int32_t> _error_count_when_initializing;
|
||||
butil::atomic<int64_t> _ema_error_cost;
|
||||
butil::atomic<int64_t> _ema_latency;
|
||||
};
|
||||
|
||||
EmaErrorRecorder _long_window;
|
||||
EmaErrorRecorder _short_window;
|
||||
int64_t _last_reset_time_ms;
|
||||
butil::atomic<int> _isolation_duration_ms;
|
||||
butil::atomic<int> _isolated_times;
|
||||
butil::atomic<bool> _broken;
|
||||
butil::atomic<bool> _half_open;
|
||||
butil::atomic<int32_t> _half_open_success_count;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif // BRPC_CIRCUIT_BREAKER_H_
|
||||
@@ -0,0 +1,74 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_CLOSURE_GUARD_H
|
||||
#define BRPC_CLOSURE_GUARD_H
|
||||
|
||||
#include <google/protobuf/service.h>
|
||||
#include "butil/macros.h"
|
||||
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// RAII: Call Run() of the closure on destruction.
|
||||
class ClosureGuard {
|
||||
public:
|
||||
ClosureGuard() : _done(NULL) {}
|
||||
|
||||
// Constructed with a closure which will be Run() inside dtor.
|
||||
explicit ClosureGuard(google::protobuf::Closure* done) : _done(done) {}
|
||||
|
||||
// Run internal closure if it's not NULL.
|
||||
~ClosureGuard() {
|
||||
if (_done) {
|
||||
_done->Run();
|
||||
}
|
||||
}
|
||||
|
||||
// Run internal closure if it's not NULL and set it to `done'.
|
||||
void reset(google::protobuf::Closure* done) {
|
||||
if (_done) {
|
||||
_done->Run();
|
||||
}
|
||||
_done = done;
|
||||
}
|
||||
|
||||
// Return and set internal closure to NULL.
|
||||
google::protobuf::Closure* release() {
|
||||
google::protobuf::Closure* const prev_done = _done;
|
||||
_done = NULL;
|
||||
return prev_done;
|
||||
}
|
||||
|
||||
// True if no closure inside.
|
||||
bool empty() const { return _done == NULL; }
|
||||
|
||||
// Exchange closure with another guard.
|
||||
void swap(ClosureGuard& other) { std::swap(_done, other._done); }
|
||||
|
||||
private:
|
||||
// Copying this object makes no sense.
|
||||
DISALLOW_COPY_AND_ASSIGN(ClosureGuard);
|
||||
|
||||
google::protobuf::Closure* _done;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_CLOSURE_GUARD_H
|
||||
@@ -0,0 +1,149 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <gflags/gflags.h>
|
||||
#include "brpc/cluster_recover_policy.h"
|
||||
#include "butil/scoped_lock.h"
|
||||
#include "butil/synchronization/lock.h"
|
||||
#include "brpc/server_id.h"
|
||||
#include "brpc/socket.h"
|
||||
#include "butil/fast_rand.h"
|
||||
#include "butil/time.h"
|
||||
#include "butil/string_splitter.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
DEFINE_int64(detect_available_server_interval_ms, 10, "The interval "
|
||||
"to detect available server count in DefaultClusterRecoverPolicy");
|
||||
|
||||
DefaultClusterRecoverPolicy::DefaultClusterRecoverPolicy(
|
||||
int64_t min_working_instances, int64_t hold_seconds)
|
||||
: _recovering(false)
|
||||
, _min_working_instances(min_working_instances)
|
||||
, _last_usable(0)
|
||||
, _last_usable_change_time_ms(0)
|
||||
, _hold_seconds(hold_seconds)
|
||||
, _usable_cache(0)
|
||||
, _usable_cache_time_ms(0) { }
|
||||
|
||||
void DefaultClusterRecoverPolicy::StartRecover() {
|
||||
std::unique_lock<butil::Mutex> mu(_mutex);
|
||||
_recovering = true;
|
||||
}
|
||||
|
||||
bool DefaultClusterRecoverPolicy::StopRecoverIfNecessary() {
|
||||
if (!_recovering) {
|
||||
return false;
|
||||
}
|
||||
int64_t now_ms = butil::cpuwide_time_ms();
|
||||
std::unique_lock<butil::Mutex> mu(_mutex);
|
||||
if (_last_usable_change_time_ms != 0 && _last_usable != 0 &&
|
||||
(now_ms - _last_usable_change_time_ms > _hold_seconds * 1000)) {
|
||||
_recovering = false;
|
||||
_last_usable = 0;
|
||||
_last_usable_change_time_ms = 0;
|
||||
mu.unlock();
|
||||
return false;
|
||||
}
|
||||
mu.unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t DefaultClusterRecoverPolicy::GetUsableServerCount(
|
||||
int64_t now_ms, const std::vector<ServerId>& server_list) {
|
||||
if (now_ms - _usable_cache_time_ms < FLAGS_detect_available_server_interval_ms) {
|
||||
return _usable_cache;
|
||||
}
|
||||
uint64_t usable = 0;
|
||||
size_t n = server_list.size();
|
||||
SocketUniquePtr ptr;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (Socket::Address(server_list[i].id, &ptr) == 0
|
||||
&& ptr->IsAvailable()) {
|
||||
usable++;
|
||||
}
|
||||
}
|
||||
{
|
||||
std::unique_lock<butil::Mutex> mu(_mutex);
|
||||
_usable_cache = usable;
|
||||
_usable_cache_time_ms = now_ms;
|
||||
}
|
||||
return _usable_cache;
|
||||
}
|
||||
|
||||
|
||||
bool DefaultClusterRecoverPolicy::DoReject(const std::vector<ServerId>& server_list) {
|
||||
if (!_recovering) {
|
||||
return false;
|
||||
}
|
||||
int64_t now_ms = butil::cpuwide_time_ms();
|
||||
uint64_t usable = GetUsableServerCount(now_ms, server_list);
|
||||
if (_last_usable != usable) {
|
||||
std::unique_lock<butil::Mutex> mu(_mutex);
|
||||
if (_last_usable != usable) {
|
||||
_last_usable = usable;
|
||||
_last_usable_change_time_ms = now_ms;
|
||||
}
|
||||
}
|
||||
if (butil::fast_rand_less_than(_min_working_instances) >= usable) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetRecoverPolicyByParams(const butil::StringPiece& params,
|
||||
std::shared_ptr<ClusterRecoverPolicy>* ptr_out) {
|
||||
int64_t min_working_instances = -1;
|
||||
int64_t hold_seconds = -1;
|
||||
bool has_meet_params = false;
|
||||
for (butil::KeyValuePairsSplitter sp(params.begin(), params.end(), ' ', '=');
|
||||
sp; ++sp) {
|
||||
if (sp.value().empty()) {
|
||||
LOG(ERROR) << "Empty value for " << sp.key() << " in lb parameter";
|
||||
return false;
|
||||
}
|
||||
if (sp.key() == "min_working_instances") {
|
||||
if (!butil::StringToInt64(sp.value(), &min_working_instances)) {
|
||||
return false;
|
||||
}
|
||||
has_meet_params = true;
|
||||
continue;
|
||||
} else if (sp.key() == "hold_seconds") {
|
||||
if (!butil::StringToInt64(sp.value(), &hold_seconds)) {
|
||||
return false;
|
||||
}
|
||||
has_meet_params = true;
|
||||
continue;
|
||||
}
|
||||
LOG(ERROR) << "Failed to set this unknown parameters " << sp.key_and_value();
|
||||
return false;
|
||||
}
|
||||
if (min_working_instances > 0 && hold_seconds > 0) {
|
||||
ptr_out->reset(
|
||||
new DefaultClusterRecoverPolicy(min_working_instances, hold_seconds));
|
||||
} else if (has_meet_params) {
|
||||
// In this case, user set some params but not in the right way, just return
|
||||
// false to let user take care of this situation.
|
||||
LOG(ERROR) << "Invalid params=`" << params << "'";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,89 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_CLUSTER_RECOVER_POLICY
|
||||
#define BRPC_CLUSTER_RECOVER_POLICY
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "butil/synchronization/lock.h"
|
||||
#include "butil/strings/string_piece.h"
|
||||
#include "butil/strings/string_number_conversions.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
struct ServerId;
|
||||
|
||||
// After all servers are down and health check happens, servers are
|
||||
// online one by one. Once one server is up, all the request that should
|
||||
// be sent to all servers, would be sent to one server, which may be a
|
||||
// disastrous behaviour. In the worst case it would cause the server being down
|
||||
// again if circuit breaker is enabled and the cluster would never recover.
|
||||
// This class controls the amount of requests that sent to the revived
|
||||
// servers when recovering from all servers are down.
|
||||
class ClusterRecoverPolicy {
|
||||
public:
|
||||
virtual ~ClusterRecoverPolicy() {}
|
||||
|
||||
// Indicate that recover from all server being down is happening.
|
||||
virtual void StartRecover() = 0;
|
||||
|
||||
// Return true if some customized policies are satisfied.
|
||||
virtual bool DoReject(const std::vector<ServerId>& server_list) = 0;
|
||||
|
||||
// Stop recover state and do not reject the request if some condition is
|
||||
// satisfied. Return true if the current state is still in recovering.
|
||||
virtual bool StopRecoverIfNecessary() = 0;
|
||||
};
|
||||
|
||||
// The default cluster recover policy. Once no servers are available, recover is start.
|
||||
// If in recover state, the probability that a request is accepted is q/n, in
|
||||
// which q is the number of current available server, n is the number of minimum
|
||||
// working instances setting by user. If q is not changed during a given time,
|
||||
// hold_seconds, then the cluster is considered recovered and all the request
|
||||
// would be sent to the current available servers.
|
||||
class DefaultClusterRecoverPolicy : public ClusterRecoverPolicy {
|
||||
public:
|
||||
DefaultClusterRecoverPolicy(int64_t min_working_instances, int64_t hold_seconds);
|
||||
|
||||
void StartRecover() override;
|
||||
bool DoReject(const std::vector<ServerId>& server_list) override;
|
||||
bool StopRecoverIfNecessary() override;
|
||||
|
||||
private:
|
||||
uint64_t GetUsableServerCount(int64_t now_ms, const std::vector<ServerId>& server_list);
|
||||
|
||||
private:
|
||||
bool _recovering;
|
||||
int64_t _min_working_instances;
|
||||
butil::Mutex _mutex;
|
||||
uint64_t _last_usable;
|
||||
int64_t _last_usable_change_time_ms;
|
||||
int64_t _hold_seconds;
|
||||
uint64_t _usable_cache;
|
||||
int64_t _usable_cache_time_ms;
|
||||
};
|
||||
|
||||
// Return a DefaultClusterRecoverPolicy object by params.
|
||||
bool GetRecoverPolicyByParams(const butil::StringPiece& params,
|
||||
std::shared_ptr<ClusterRecoverPolicy>* ptr_out);
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#include "butil/logging.h"
|
||||
#include "json2pb/json_to_pb.h"
|
||||
#include "brpc/compress.h"
|
||||
#include "brpc/protocol.h"
|
||||
#include "brpc/proto_base.pb.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
static const int MAX_HANDLER_SIZE = 1024;
|
||||
static CompressHandler s_handler_map[MAX_HANDLER_SIZE] = { { NULL, NULL, NULL } };
|
||||
|
||||
int RegisterCompressHandler(CompressType type,
|
||||
CompressHandler handler) {
|
||||
if (NULL == handler.Compress || NULL == handler.Decompress) {
|
||||
LOG(FATAL) << "Invalid parameter: handler function is NULL";
|
||||
return -1;
|
||||
}
|
||||
int index = type;
|
||||
if (index < 0 || index >= MAX_HANDLER_SIZE) {
|
||||
LOG(FATAL) << "CompressType=" << type << " is out of range";
|
||||
return -1;
|
||||
}
|
||||
if (s_handler_map[index].Compress != NULL) {
|
||||
LOG(FATAL) << "CompressType=" << type << " was registered";
|
||||
return -1;
|
||||
}
|
||||
s_handler_map[index] = handler;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find CompressHandler by type.
|
||||
// Returns NULL if not found
|
||||
const CompressHandler* FindCompressHandler(CompressType type) {
|
||||
int index = type;
|
||||
if (index < 0 || index >= MAX_HANDLER_SIZE) {
|
||||
LOG(ERROR) << "CompressType=" << type << " is out of range";
|
||||
return NULL;
|
||||
}
|
||||
if (NULL == s_handler_map[index].Compress) {
|
||||
return NULL;
|
||||
}
|
||||
return &s_handler_map[index];
|
||||
}
|
||||
|
||||
const char* CompressTypeToCStr(CompressType type) {
|
||||
if (type == COMPRESS_TYPE_NONE) {
|
||||
return "none";
|
||||
}
|
||||
const CompressHandler* handler = FindCompressHandler(type);
|
||||
return (handler != NULL ? handler->name : "unknown");
|
||||
}
|
||||
|
||||
void ListCompressHandler(std::vector<CompressHandler>* vec) {
|
||||
vec->clear();
|
||||
for (int i = 0; i < MAX_HANDLER_SIZE; ++i) {
|
||||
if (s_handler_map[i].Compress != NULL) {
|
||||
vec->push_back(s_handler_map[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ParseFromCompressedData(const butil::IOBuf& data,
|
||||
google::protobuf::Message* msg,
|
||||
CompressType compress_type) {
|
||||
if (compress_type == COMPRESS_TYPE_NONE) {
|
||||
return ParsePbFromIOBuf(msg, data);
|
||||
}
|
||||
const CompressHandler* handler = FindCompressHandler(compress_type);
|
||||
if (NULL == handler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Deserializer deserializer([msg](google::protobuf::io::ZeroCopyInputStream* input) {
|
||||
return msg->ParseFromZeroCopyStream(input);
|
||||
});
|
||||
return handler->Decompress(data, &deserializer);
|
||||
}
|
||||
|
||||
bool SerializeAsCompressedData(const google::protobuf::Message& msg,
|
||||
butil::IOBuf* buf, CompressType compress_type) {
|
||||
if (compress_type == COMPRESS_TYPE_NONE) {
|
||||
butil::IOBufAsZeroCopyOutputStream wrapper(buf);
|
||||
return msg.SerializeToZeroCopyStream(&wrapper);
|
||||
}
|
||||
const CompressHandler* handler = FindCompressHandler(compress_type);
|
||||
if (NULL == handler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Serializer serializer([&msg](google::protobuf::io::ZeroCopyOutputStream* output) {
|
||||
return msg.SerializeToZeroCopyStream(output);
|
||||
});
|
||||
return handler->Compress(serializer, buf);
|
||||
}
|
||||
|
||||
::google::protobuf::Metadata Serializer::GetMetadata() const {
|
||||
::google::protobuf::Metadata metadata{};
|
||||
metadata.descriptor = SerializerBase::descriptor();
|
||||
metadata.reflection = nullptr;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
::google::protobuf::Metadata Deserializer::GetMetadata() const {
|
||||
::google::protobuf::Metadata metadata{};
|
||||
metadata.descriptor = DeserializerBase::descriptor();
|
||||
metadata.reflection = nullptr;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,202 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_COMPRESS_H
|
||||
#define BRPC_COMPRESS_H
|
||||
|
||||
#include <google/protobuf/message.h> // Message
|
||||
#include "butil/iobuf.h" // butil::IOBuf
|
||||
#include "butil/logging.h"
|
||||
#include "brpc/options.pb.h" // CompressType
|
||||
#include "brpc/nonreflectable_message.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Serializer can be used to implement custom serialization
|
||||
// before compression with user callback.
|
||||
class Serializer : public NonreflectableMessage<Serializer> {
|
||||
public:
|
||||
using Callback = std::function<bool(google::protobuf::io::ZeroCopyOutputStream*)>;
|
||||
|
||||
Serializer() :Serializer(NULL) {}
|
||||
|
||||
explicit Serializer(Callback callback)
|
||||
:_callback(std::move(callback)) {
|
||||
SharedCtor();
|
||||
}
|
||||
|
||||
~Serializer() override {
|
||||
SharedDtor();
|
||||
}
|
||||
|
||||
Serializer(const Serializer& from)
|
||||
: NonreflectableMessage(from) {
|
||||
SharedCtor();
|
||||
MergeFrom(from);
|
||||
}
|
||||
|
||||
Serializer& operator=(const Serializer& from) {
|
||||
CopyFrom(from);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Swap(Serializer* other) {
|
||||
if (other != this) {
|
||||
}
|
||||
}
|
||||
|
||||
void MergeFrom(const Serializer& from) override {
|
||||
CHECK_NE(&from, this);
|
||||
}
|
||||
|
||||
// implements Message ----------------------------------------------
|
||||
void Clear() override {
|
||||
_callback = nullptr;
|
||||
}
|
||||
size_t ByteSizeLong() const override { return 0; }
|
||||
int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); }
|
||||
|
||||
::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE;
|
||||
|
||||
// Converts the data into `output' for later compression.
|
||||
bool SerializeTo(google::protobuf::io::ZeroCopyOutputStream* output) const {
|
||||
if (!_callback) {
|
||||
LOG(WARNING) << "Serializer::SerializeTo() called without callback";
|
||||
return false;
|
||||
}
|
||||
return _callback(output);
|
||||
}
|
||||
|
||||
void SetCallback(Callback callback) {
|
||||
_callback = std::move(callback);
|
||||
}
|
||||
|
||||
private:
|
||||
void SharedCtor() {}
|
||||
void SharedDtor() {}
|
||||
|
||||
Callback _callback;
|
||||
};
|
||||
|
||||
// Deserializer can be used to implement custom deserialization
|
||||
// after decompression with user callback.
|
||||
class Deserializer : public NonreflectableMessage<Deserializer> {
|
||||
public:
|
||||
public:
|
||||
using Callback = std::function<bool(google::protobuf::io::ZeroCopyInputStream*)>;
|
||||
|
||||
Deserializer() :Deserializer(NULL) {}
|
||||
|
||||
explicit Deserializer(Callback callback) : _callback(std::move(callback)) {
|
||||
SharedCtor();
|
||||
}
|
||||
|
||||
~Deserializer() override {
|
||||
SharedDtor();
|
||||
}
|
||||
|
||||
Deserializer(const Deserializer& from)
|
||||
: NonreflectableMessage(from) {
|
||||
SharedCtor();
|
||||
MergeFrom(from);
|
||||
}
|
||||
|
||||
Deserializer& operator=(const Deserializer& from) {
|
||||
CopyFrom(from);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Swap(Deserializer* other) {
|
||||
if (other != this) {
|
||||
_callback.swap(other->_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void MergeFrom(const Deserializer& from) override {
|
||||
CHECK_NE(&from, this);
|
||||
_callback = from._callback;
|
||||
}
|
||||
|
||||
// implements Message ----------------------------------------------
|
||||
void Clear() override { _callback = nullptr; }
|
||||
size_t ByteSizeLong() const override { return 0; }
|
||||
int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); }
|
||||
|
||||
::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE;
|
||||
|
||||
// Converts the decompressed `input'.
|
||||
bool DeserializeFrom(google::protobuf::io::ZeroCopyInputStream* intput) const {
|
||||
if (!_callback) {
|
||||
LOG(WARNING) << "Deserializer::DeserializeFrom() called without callback";
|
||||
return false;
|
||||
}
|
||||
return _callback(intput);
|
||||
}
|
||||
void SetCallback(Callback callback) {
|
||||
_callback = std::move(callback);
|
||||
}
|
||||
|
||||
private:
|
||||
void SharedCtor() {}
|
||||
void SharedDtor() {}
|
||||
|
||||
Callback _callback;
|
||||
};
|
||||
|
||||
struct CompressHandler {
|
||||
// Compress serialized `msg' into `buf'.
|
||||
// Returns true on success, false otherwise
|
||||
bool (*Compress)(const google::protobuf::Message& msg, butil::IOBuf* buf);
|
||||
|
||||
// Parse decompressed `data' as `msg'.
|
||||
// Returns true on success, false otherwise
|
||||
bool (*Decompress)(const butil::IOBuf& data, google::protobuf::Message* msg);
|
||||
|
||||
// Name of the compression algorithm, must be string constant.
|
||||
const char* name;
|
||||
};
|
||||
|
||||
// [NOT thread-safe] Register `handler' using key=`type'
|
||||
// Returns 0 on success, -1 otherwise
|
||||
int RegisterCompressHandler(CompressType type, CompressHandler handler);
|
||||
|
||||
// Returns CompressHandler pointer of `type' if registered, NULL otherwise.
|
||||
const CompressHandler* FindCompressHandler(CompressType type);
|
||||
|
||||
// Returns the `name' of the CompressType if registered
|
||||
const char* CompressTypeToCStr(CompressType type);
|
||||
|
||||
// Put all registered handlers into `vec'.
|
||||
void ListCompressHandler(std::vector<CompressHandler>* vec);
|
||||
|
||||
// Parse decompressed `data' as `msg' using registered `compress_type'.
|
||||
// Returns true on success, false otherwise
|
||||
bool ParseFromCompressedData(const butil::IOBuf& data,
|
||||
google::protobuf::Message* msg,
|
||||
CompressType compress_type);
|
||||
|
||||
// Compress serialized `msg' into `buf' using registered `compress_type'.
|
||||
// Returns true on success, false otherwise
|
||||
bool SerializeAsCompressedData(const google::protobuf::Message& msg,
|
||||
butil::IOBuf* buf,
|
||||
CompressType compress_type);
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_COMPRESS_H
|
||||
@@ -0,0 +1,65 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_CONCURRENCY_LIMITER_H
|
||||
#define BRPC_CONCURRENCY_LIMITER_H
|
||||
|
||||
#include "brpc/describable.h"
|
||||
#include "brpc/destroyable.h"
|
||||
#include "brpc/extension.h" // Extension<T>
|
||||
#include "brpc/adaptive_max_concurrency.h" // AdaptiveMaxConcurrency
|
||||
#include "brpc/controller.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class ConcurrencyLimiter {
|
||||
public:
|
||||
virtual ~ConcurrencyLimiter() {}
|
||||
|
||||
// This method should be called each time a request comes in. It returns
|
||||
// false when the concurrency reaches the upper limit, otherwise it
|
||||
// returns true. Normally, when OnRequested returns false, you should
|
||||
// return an ELIMIT error directly.
|
||||
virtual bool OnRequested(int current_concurrency, Controller* cntl) = 0;
|
||||
|
||||
// Each request should call this method before responding.
|
||||
// `error_code' : Error code obtained from the controller, 0 means success.
|
||||
// `latency' : Microseconds taken by RPC.
|
||||
// NOTE: Even if OnRequested returns false, after sending ELIMIT, you
|
||||
// still need to call OnResponded.
|
||||
virtual void OnResponded(int error_code, int64_t latency_us) = 0;
|
||||
|
||||
// Returns the latest max_concurrency.
|
||||
// The return value is only for logging.
|
||||
virtual int MaxConcurrency() = 0;
|
||||
|
||||
// Reset max_concurrency
|
||||
virtual int ResetMaxConcurrency(const AdaptiveMaxConcurrency& amc) = 0;
|
||||
|
||||
// Create an instance from the amc
|
||||
// Caller is responsible for delete the instance after usage.
|
||||
virtual ConcurrencyLimiter* New(const AdaptiveMaxConcurrency& amc) const = 0;
|
||||
};
|
||||
|
||||
inline Extension<const ConcurrencyLimiter>* ConcurrencyLimiterExtension() {
|
||||
return Extension<const ConcurrencyLimiter>::instance();
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
|
||||
#endif // BRPC_CONCURRENCY_LIMITER_H
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,148 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_COROUTINE_H
|
||||
#define BRPC_COROUTINE_H
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
|
||||
#define BRPC_ENABLE_COROUTINE 1
|
||||
|
||||
#include <coroutine>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include "brpc/callback.h"
|
||||
|
||||
namespace brpc {
|
||||
namespace experimental {
|
||||
|
||||
namespace detail {
|
||||
class AwaitablePromiseBase;
|
||||
template <typename T>
|
||||
class AwaitablePromise;
|
||||
}
|
||||
|
||||
class AwaitableDone;
|
||||
class Coroutine;
|
||||
|
||||
// WARN:The bRPC coroutine feature is experimental, DO NOT use in production environment!
|
||||
|
||||
// Awaitable<T> is used as coroutine return type, for example:
|
||||
// Awaitable<int> func1() {
|
||||
// co_return 42;
|
||||
// }
|
||||
// Awaitable<std::string> func2() {
|
||||
// int ret = co_await func1();
|
||||
// co_return std::to_string(ret);
|
||||
// }
|
||||
template <typename T>
|
||||
class Awaitable {
|
||||
public:
|
||||
using promise_type = detail::AwaitablePromise<T>;
|
||||
|
||||
~Awaitable() {}
|
||||
|
||||
// NOTE: compiler will generate calls to these functions automatically,
|
||||
// DO NOT call them manually
|
||||
bool await_ready();
|
||||
template <typename U>
|
||||
void await_suspend(std::coroutine_handle<detail::AwaitablePromise<U> > awaiting);
|
||||
T await_resume();
|
||||
|
||||
private:
|
||||
friend class detail::AwaitablePromise<T>;
|
||||
friend class AwaitableDone;
|
||||
friend class Coroutine;
|
||||
|
||||
Awaitable() = delete;
|
||||
Awaitable(promise_type* p) : _promise(p) {}
|
||||
|
||||
promise_type* promise() {
|
||||
return _promise;
|
||||
}
|
||||
|
||||
promise_type* _promise;
|
||||
};
|
||||
|
||||
// Utility for a coroutine to wait for RPC call. Usage:
|
||||
// AwaitableDone done;
|
||||
// stub.CallMethod(&cntl, &req, &resp, &done);
|
||||
// co_await done.awaitable();
|
||||
//
|
||||
class AwaitableDone : public google::protobuf::Closure {
|
||||
public:
|
||||
AwaitableDone();
|
||||
|
||||
void Run() override;
|
||||
|
||||
Awaitable<void>& awaitable() {
|
||||
return _awaitable;
|
||||
}
|
||||
private:
|
||||
Awaitable<void> _awaitable;
|
||||
};
|
||||
|
||||
// Class for management of coroutine
|
||||
// 1. To create a new coroutine and wait it finish:
|
||||
// Awaitable<void> func(double val);
|
||||
//
|
||||
// int main() {
|
||||
// Coroutine coro(func(1.0));
|
||||
// coro.join();
|
||||
// }
|
||||
// 2. To wait a coroutine in another coroutine:
|
||||
// Awaitable<void> another_func() {
|
||||
// Coroutine coro(func(1.0));
|
||||
// co_await coro.awaitable<void>();
|
||||
// }
|
||||
// 3. To create a detached coroutine without waiting:
|
||||
// Coroutine coro(func(1.0), true);
|
||||
// 4. To sleep in a coroutine:
|
||||
// co_await Coroutine::usleep(100);
|
||||
//
|
||||
// NOTE: Inside coroutine function, DO NOT call pthread-blocking or
|
||||
// bthread-blocking functions (eg. bthread_join(), bthread_usleep(), syncronized RPC),
|
||||
// otherwise may cause dead lock or long latency.
|
||||
class Coroutine {
|
||||
public:
|
||||
template <typename T>
|
||||
Coroutine(Awaitable<T>&& aw, bool detach = false);
|
||||
|
||||
~Coroutine();
|
||||
|
||||
template <typename T = void>
|
||||
T join();
|
||||
|
||||
template <typename T = void>
|
||||
Awaitable<T> awaitable();
|
||||
|
||||
static Awaitable<int> usleep(int sleep_us);
|
||||
|
||||
private:
|
||||
detail::AwaitablePromiseBase* _promise{nullptr};
|
||||
bool _waited{false};
|
||||
std::atomic<int>* _butex{nullptr};
|
||||
};
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace brpc
|
||||
|
||||
#include "brpc/coroutine_inl.h"
|
||||
|
||||
#endif // __cplusplus >= 202002L
|
||||
|
||||
#endif // BRPC_COROUTINE_H
|
||||
@@ -0,0 +1,312 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_COROUTINE_INL_H
|
||||
#define BRPC_COROUTINE_INL_H
|
||||
|
||||
#include "bthread/unstable.h" // bthread_timer_add
|
||||
#include "bthread/butex.h" // butex_wake/butex_wait
|
||||
|
||||
namespace brpc {
|
||||
namespace experimental {
|
||||
|
||||
namespace detail {
|
||||
|
||||
class AwaitablePromiseBase {
|
||||
public:
|
||||
AwaitablePromiseBase() {
|
||||
}
|
||||
|
||||
virtual ~AwaitablePromiseBase() {
|
||||
delete _suspended_or_done;
|
||||
}
|
||||
|
||||
virtual void resume() = 0;
|
||||
virtual void destroy() = 0;
|
||||
|
||||
bool needs_suspend() {
|
||||
return _suspended_or_done != nullptr;
|
||||
}
|
||||
|
||||
void set_needs_suspend() {
|
||||
_suspended_or_done = new std::atomic<bool>();
|
||||
_suspended_or_done->store(false);
|
||||
}
|
||||
|
||||
// For a Coroutine's leaf function
|
||||
// Its caller will be suspended, waiting for its done.
|
||||
// But the suspend and done are always in different threads.
|
||||
// It may suspend before done, or done before suspend.
|
||||
// So we use an atomic<bool>, after first suspend_or_done() it will become true.
|
||||
// Then the second suspend_or_done(), exchange(true) will returns true.
|
||||
// Then we can safely delete this.
|
||||
void suspend_or_done() {
|
||||
if (_suspended_or_done->exchange(true)) {
|
||||
// Already suspend AND done
|
||||
if (_caller) {
|
||||
// The leaf function has finished, resume its caller.
|
||||
_caller->resume();
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void on_suspend() { suspend_or_done(); }
|
||||
void on_done() { suspend_or_done(); }
|
||||
|
||||
void set_callback(std::function<void()> cb) {
|
||||
_callback = cb;
|
||||
}
|
||||
|
||||
void set_caller(AwaitablePromiseBase* caller) {
|
||||
_caller = caller;
|
||||
}
|
||||
|
||||
// When the coroutine function begins, initial_suspend() will be called
|
||||
auto initial_suspend() {
|
||||
// Always suspend the function, later resume() will make it start to run
|
||||
return std::suspend_always{};
|
||||
}
|
||||
|
||||
// When the coroutine function throws unhandled exception, unhandled_exception() will be called
|
||||
void unhandled_exception() {
|
||||
LOG(ERROR) << "Coroutine throws unhandled exception!";
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
// When the coroutine function ends, final_suspend() will be called
|
||||
auto final_suspend() noexcept {
|
||||
if (_caller) {
|
||||
// The caller is waiting for this function to return
|
||||
// Now it can be resumed
|
||||
_caller->resume();
|
||||
}
|
||||
if (_callback) {
|
||||
_callback();
|
||||
_callback = nullptr;
|
||||
}
|
||||
// Returns suspend_never{} so that the coroutine will be destroyed and the AwaitablePromise be deleted.
|
||||
// DO NOT call destroy() here, which will cause double destruct of RAII objects.
|
||||
// DO NOT call delete this here, which will cause malloc and free not match.
|
||||
return std::suspend_never{};
|
||||
}
|
||||
|
||||
private:
|
||||
// For a Coroutine's root function, it needs a callback to notify its waiter
|
||||
std::function<void()> _callback;
|
||||
// For a Coroutine's leaf function, it is always resumed from another thread.
|
||||
// It needs an atomic variable to keep thread safety.
|
||||
// Non-leaf function does't need this, so we defined it as an optional pointer.
|
||||
std::atomic<bool>* _suspended_or_done{nullptr};
|
||||
// For a Coroutine's non-root function, it needs to resume its caller when it finished.
|
||||
AwaitablePromiseBase* _caller{nullptr};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class AwaitablePromise : public AwaitablePromiseBase {
|
||||
public:
|
||||
T value() {
|
||||
return _value;
|
||||
}
|
||||
|
||||
void set_value(T value) {
|
||||
_value = value;
|
||||
}
|
||||
|
||||
void resume() override {
|
||||
_coro.resume();
|
||||
}
|
||||
|
||||
void destroy() override {
|
||||
_coro.destroy();
|
||||
}
|
||||
|
||||
// When we call a coroutine function, an AwaitablePromise<T> will be created.
|
||||
// Then call its get_return_object() to return an Awaitable<T>.
|
||||
auto get_return_object() {
|
||||
_coro = std::coroutine_handle<AwaitablePromise>::from_promise(*this);
|
||||
return Awaitable<T>(this);
|
||||
}
|
||||
|
||||
// When we call co_return in a function, return_value() will be called.
|
||||
auto return_value(T v) {
|
||||
_value = v;
|
||||
return std::suspend_never{};
|
||||
}
|
||||
|
||||
private:
|
||||
T _value;
|
||||
std::coroutine_handle<AwaitablePromise> _coro;
|
||||
};
|
||||
|
||||
template <>
|
||||
class AwaitablePromise<void> : public AwaitablePromiseBase {
|
||||
public:
|
||||
void resume() override {
|
||||
_coro.resume();
|
||||
}
|
||||
|
||||
void destroy() override {
|
||||
_coro.destroy();
|
||||
}
|
||||
|
||||
// When we call a coroutine function, an AwaitablePromise<void> will be created.
|
||||
// Then call its get_return_object() to return an Awaitable<void>.
|
||||
auto get_return_object() {
|
||||
_coro = std::coroutine_handle<AwaitablePromise>::from_promise(*this);
|
||||
return Awaitable<void>(this);
|
||||
}
|
||||
|
||||
// When we call return in a coroutine function, return_value() will be called.
|
||||
auto return_value() {
|
||||
return std::suspend_never{};
|
||||
}
|
||||
|
||||
private:
|
||||
std::coroutine_handle<AwaitablePromise> _coro;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// When co_await an Awaitable<T>, await_ready() will be called automatically.
|
||||
template <typename T>
|
||||
inline bool Awaitable<T>::await_ready() {
|
||||
// Always returns false so that the caller will be suspended at the co_await point.
|
||||
return false;
|
||||
}
|
||||
|
||||
// If await_ready returns false, await_suspend() will be called automatically.
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
inline void Awaitable<T>::await_suspend(std::coroutine_handle<detail::AwaitablePromise<U> > awaiting) {
|
||||
_promise->set_caller(&awaiting.promise());
|
||||
if (_promise->needs_suspend()) {
|
||||
_promise->on_suspend();
|
||||
return;
|
||||
}
|
||||
_promise->resume();
|
||||
}
|
||||
|
||||
// When the caller resumes from co_await, await_resume() will be called to get return value
|
||||
template <typename T>
|
||||
inline T Awaitable<T>::await_resume() {
|
||||
if constexpr (!std::is_same<T, void>::value) {
|
||||
return _promise->value();
|
||||
}
|
||||
}
|
||||
|
||||
inline AwaitableDone::AwaitableDone()
|
||||
: _awaitable(new detail::AwaitablePromise<void>) {
|
||||
_awaitable.promise()->set_needs_suspend();
|
||||
}
|
||||
|
||||
inline void AwaitableDone::Run() {
|
||||
_awaitable.promise()->on_done();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Coroutine::Coroutine(Awaitable<T>&& aw, bool detach) {
|
||||
detail::AwaitablePromise<T>* origin_promise = aw.promise();
|
||||
CHECK(origin_promise);
|
||||
|
||||
if (!detach) {
|
||||
// Create butex for join()
|
||||
_butex = bthread::butex_create_checked<std::atomic<int> >();
|
||||
_butex->store(0);
|
||||
|
||||
// Create AwaitablePromise for awaitable()
|
||||
_promise = new detail::AwaitablePromise<T>();
|
||||
_promise->set_needs_suspend();
|
||||
|
||||
auto cb = [this, origin_promise]() {
|
||||
if constexpr (!std::is_same<T, void>::value) {
|
||||
dynamic_cast<detail::AwaitablePromise<T>*>(_promise)->set_value(origin_promise->value());
|
||||
}
|
||||
// wakeup join()
|
||||
_butex->store(1);
|
||||
bthread::butex_wake(_butex);
|
||||
|
||||
// wakeup co_await on awaitable()
|
||||
_promise->on_done();
|
||||
};
|
||||
origin_promise->set_callback(cb);
|
||||
}
|
||||
|
||||
// Start to run the coroutine
|
||||
origin_promise->resume();
|
||||
}
|
||||
|
||||
inline Coroutine::~Coroutine() {
|
||||
if (_promise != nullptr && !_waited) {
|
||||
join();
|
||||
}
|
||||
if (_butex) {
|
||||
bthread::butex_destroy(_butex);
|
||||
_butex = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T Coroutine::join() {
|
||||
CHECK(_promise != nullptr) << "join() can not be called to detached coroutine!";
|
||||
CHECK(_waited == false) << "awaitable() or join() can only be called once!";
|
||||
_waited = true;
|
||||
bthread::butex_wait(_butex, 0, nullptr);
|
||||
if constexpr (!std::is_same<T, void>::value) {
|
||||
auto promise = dynamic_cast<detail::AwaitablePromise<T>*>(_promise);
|
||||
CHECK(promise != nullptr) << "join type not match";
|
||||
T ret = promise->value();
|
||||
_promise->on_suspend();
|
||||
return ret;
|
||||
} else {
|
||||
_promise->on_suspend();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Awaitable<T> Coroutine::awaitable() {
|
||||
CHECK(_promise != nullptr) << "awaitable() can not be called to detached coroutine!";
|
||||
CHECK(_waited == false) << "awaitable() or join() can only be called once!";
|
||||
auto promise = dynamic_cast<detail::AwaitablePromise<T>*>(_promise);
|
||||
CHECK(promise != nullptr) << "awaitable type not match";
|
||||
_waited = true;
|
||||
return Awaitable<T>(promise);
|
||||
}
|
||||
|
||||
// NOTE: the caller will be resumed on bthread timer thread,
|
||||
// bthread only have one timer thread, this may be performance bottle-neck
|
||||
inline Awaitable<int> Coroutine::usleep(int sleep_us) {
|
||||
auto promise = new detail::AwaitablePromise<int>();
|
||||
promise->set_needs_suspend();
|
||||
bthread_timer_t timer;
|
||||
auto abstime = butil::microseconds_from_now(sleep_us);
|
||||
auto cb = [](void* p) {
|
||||
auto promise = static_cast<detail::AwaitablePromise<int>*>(p);
|
||||
promise->set_value(0);
|
||||
promise->on_done();
|
||||
};
|
||||
if (bthread_timer_add(&timer, abstime, cb, promise) != 0) {
|
||||
promise->set_value(-1);
|
||||
promise->on_done();
|
||||
}
|
||||
return Awaitable<int>(promise);
|
||||
}
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace brpc
|
||||
|
||||
#endif // BRPC_COROUTINE_INL_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,517 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#ifndef BRPC_COUCHBASE_H
|
||||
#define BRPC_COUCHBASE_H
|
||||
|
||||
#endif
|
||||
|
||||
#include <brpc/channel.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "brpc/nonreflectable_message.h"
|
||||
#include "brpc/pb_compat.h"
|
||||
#include "butil/iobuf.h"
|
||||
#include "butil/strings/string_piece.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// Forward declarations for friend functions
|
||||
class InputMessageBase;
|
||||
class Controller;
|
||||
namespace policy {
|
||||
void ProcessCouchbaseResponse(InputMessageBase* msg);
|
||||
void SerializeCouchbaseRequest(butil::IOBuf* buf, Controller* cntl,
|
||||
const google::protobuf::Message* request);
|
||||
} // namespace policy
|
||||
|
||||
// Simple C++11 compatible reader-writer lock
|
||||
class ReaderWriterLock {
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::condition_variable reader_cv_;
|
||||
std::condition_variable writer_cv_;
|
||||
std::atomic<int> reader_count_;
|
||||
std::atomic<bool> writer_active_;
|
||||
std::atomic<int> waiting_writers_;
|
||||
|
||||
public:
|
||||
ReaderWriterLock()
|
||||
: reader_count_(0), writer_active_(false), waiting_writers_(0) {}
|
||||
|
||||
void lock_shared() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
reader_cv_.wait(lock, [this] {
|
||||
return !writer_active_.load() && waiting_writers_.load() == 0;
|
||||
});
|
||||
reader_count_.fetch_add(1);
|
||||
}
|
||||
|
||||
void unlock_shared() {
|
||||
reader_count_.fetch_sub(1);
|
||||
if (reader_count_.load() == 0) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
writer_cv_.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void lock() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
waiting_writers_.fetch_add(1);
|
||||
writer_cv_.wait(lock, [this] {
|
||||
return !writer_active_.load() && reader_count_.load() == 0;
|
||||
});
|
||||
waiting_writers_.fetch_sub(1);
|
||||
writer_active_.store(true);
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
writer_active_.store(false);
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
writer_cv_.notify_one();
|
||||
reader_cv_.notify_all();
|
||||
}
|
||||
};
|
||||
|
||||
// RAII helper classes
|
||||
class SharedLock {
|
||||
private:
|
||||
ReaderWriterLock& lock_;
|
||||
|
||||
public:
|
||||
explicit SharedLock(ReaderWriterLock& lock) : lock_(lock) {
|
||||
lock_.lock_shared();
|
||||
}
|
||||
~SharedLock() { lock_.unlock_shared(); }
|
||||
};
|
||||
|
||||
class UniqueLock {
|
||||
private:
|
||||
ReaderWriterLock& lock_;
|
||||
|
||||
public:
|
||||
explicit UniqueLock(ReaderWriterLock& lock) : lock_(lock) { lock_.lock(); }
|
||||
~UniqueLock() { lock_.unlock(); }
|
||||
};
|
||||
|
||||
// manager
|
||||
class CouchbaseManifestManager {
|
||||
public:
|
||||
struct CollectionManifest {
|
||||
std::string uid; // uid of the manifest, it can be used to track if the manifest
|
||||
// is updated
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, uint8_t>>
|
||||
scope_to_collection_id_map; // scope -> (collection -> collection_id)
|
||||
};
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string /*server*/,
|
||||
std::unordered_map<std::string /*bucket*/, CollectionManifest>>
|
||||
bucket_to_collection_manifest_;
|
||||
ReaderWriterLock rw_bucket_to_collection_manifest_mutex_;
|
||||
|
||||
public:
|
||||
CouchbaseManifestManager() {}
|
||||
~CouchbaseManifestManager() { bucket_to_collection_manifest_.clear(); }
|
||||
bool setBucketToCollectionManifest(std::string server, std::string bucket,
|
||||
CollectionManifest manifest);
|
||||
|
||||
bool getBucketToCollectionManifest(std::string server, std::string bucket,
|
||||
CollectionManifest* manifest);
|
||||
bool getManifestToCollectionId(CollectionManifest* manifest, std::string scope,
|
||||
std::string collection, uint8_t* collection_id);
|
||||
|
||||
bool jsonToCollectionManifest(const std::string& json,
|
||||
CollectionManifest* manifest);
|
||||
bool refreshCollectionManifest(
|
||||
brpc::Channel* channel, const std::string& server, const std::string& bucket,
|
||||
std::unordered_map<std::string, CollectionManifest>* local_cache = nullptr);
|
||||
} static common_metadata_tracking;
|
||||
class CouchbaseOperations {
|
||||
public:
|
||||
enum operation_type {
|
||||
GET = 1,
|
||||
UPSERT = 2,
|
||||
ADD = 3,
|
||||
REPLACE = 4,
|
||||
APPEND = 5,
|
||||
PREPEND = 6,
|
||||
DELETE = 7
|
||||
};
|
||||
struct Result {
|
||||
bool success;
|
||||
std::string error_message;
|
||||
std::string value;
|
||||
uint16_t status_code; // 0x00 if success
|
||||
};
|
||||
Result get(const std::string& key, std::string collection_name = "_default");
|
||||
Result upsert(const std::string& key, const std::string& value,
|
||||
std::string collection_name = "_default");
|
||||
Result add(const std::string& key, const std::string& value,
|
||||
std::string collection_name = "_default");
|
||||
// Warning: Not tested
|
||||
// Result replace(const std::string& key, const std::string& value, std::string
|
||||
// collection_name = "_default");
|
||||
Result append(const std::string& key, const std::string& value,
|
||||
std::string collection_name = "_default");
|
||||
Result prepend(const std::string& key, const std::string& value,
|
||||
std::string collection_name = "_default");
|
||||
Result delete_(const std::string& key, std::string collection_name = "_default");
|
||||
// Warning: Not tested
|
||||
// Result Increment(const string& key, uint64_t delta, uint64_t initial_value,
|
||||
// uint32_t exptime, string collection_name = "_default"); Result
|
||||
// Decrement(const string& key, uint64_t delta, uint64_t initial_value,
|
||||
// uint32_t exptime, string collection_name = "_default"); Result Touch(const
|
||||
// string& key, uint32_t exptime, string collection_name = "_default"); Result
|
||||
// Flush(uint32_t timeout = 0);
|
||||
Result version();
|
||||
Result authenticateSSL(const std::string& username, const std::string& password,
|
||||
const std::string& server_address,
|
||||
const std::string& bucket_name, std::string path_to_cert = "");
|
||||
Result authenticate(const std::string& username, const std::string& password,
|
||||
const std::string& server_address, const std::string& bucket_name);
|
||||
Result selectBucket(const std::string& bucket_name);
|
||||
|
||||
// Pipeline management
|
||||
bool beginPipeline();
|
||||
bool pipelineRequest(operation_type op_type, const std::string& key,
|
||||
const std::string& value = "",
|
||||
std::string collection_name = "_default");
|
||||
std::vector<Result> executePipeline(); // Return by value instead of pointer
|
||||
bool clearPipeline();
|
||||
|
||||
// Pipeline status
|
||||
bool isPipelineActive() const { return pipeline_active; }
|
||||
size_t getPipelineSize() const { return pipeline_operations_queue.size(); }
|
||||
|
||||
CouchbaseOperations()
|
||||
: pipeline_request_couchbase_req(&local_bucket_to_collection_manifest_),
|
||||
pipeline_active(false) {}
|
||||
~CouchbaseOperations() {}
|
||||
bool getLocalCachedCollectionId(const std::string& bucket, const std::string& scope,
|
||||
const std::string& collection, uint8_t* coll_id);
|
||||
|
||||
private:
|
||||
CouchbaseOperations::Result authenticateAll(const std::string& username,
|
||||
const std::string& password,
|
||||
const std::string& server_address,
|
||||
const std::string& bucket_name,
|
||||
bool enable_ssl,
|
||||
std::string path_to_cert);
|
||||
friend void policy::ProcessCouchbaseResponse(InputMessageBase* msg);
|
||||
friend void policy::SerializeCouchbaseRequest(
|
||||
butil::IOBuf* buf, Controller* cntl,
|
||||
const google::protobuf::Message* request);
|
||||
brpc::Channel* channel_;
|
||||
std::string server_address_;
|
||||
std::string selected_bucket_;
|
||||
|
||||
std::unordered_map<std::string /*bucket*/, CouchbaseManifestManager::CollectionManifest>
|
||||
local_bucket_to_collection_manifest_;
|
||||
|
||||
public:
|
||||
// these classes have been made public so that normal user can also create
|
||||
// advanced bRPC programs as per their requirements.
|
||||
class CouchbaseRequest : public NonreflectableMessage<CouchbaseRequest> {
|
||||
public:
|
||||
static brpc::CouchbaseManifestManager* metadata_tracking;
|
||||
int _pipelined_count;
|
||||
butil::IOBuf _buf;
|
||||
mutable int _cached_size_;
|
||||
void sharedCtor();
|
||||
void sharedDtor();
|
||||
void setCachedSize(int size) const;
|
||||
bool getOrDelete(uint8_t command, const butil::StringPiece& key,
|
||||
uint8_t coll_id = 0);
|
||||
bool counter(uint8_t command, const butil::StringPiece& key, uint64_t delta,
|
||||
uint64_t initial_value, uint32_t exptime);
|
||||
|
||||
bool store(uint8_t command, const butil::StringPiece& key,
|
||||
const butil::StringPiece& value, uint32_t flags,
|
||||
uint32_t exptime, uint64_t cas_value, uint8_t coll_id = 0);
|
||||
uint32_t hashCrc32(const char* key, size_t key_length);
|
||||
|
||||
public:
|
||||
std::unordered_map<std::string /*bucket*/,
|
||||
CouchbaseManifestManager::CollectionManifest>*
|
||||
local_collection_manifest_cache;
|
||||
|
||||
CouchbaseRequest(
|
||||
std::unordered_map<std::string /*bucket*/,
|
||||
CouchbaseManifestManager::CollectionManifest>*
|
||||
local_cache_reference)
|
||||
: NonreflectableMessage<CouchbaseRequest>() {
|
||||
metadata_tracking = &common_metadata_tracking;
|
||||
local_collection_manifest_cache = local_cache_reference;
|
||||
sharedCtor();
|
||||
}
|
||||
CouchbaseRequest() : NonreflectableMessage<CouchbaseRequest>() {
|
||||
metadata_tracking = &common_metadata_tracking;
|
||||
sharedCtor();
|
||||
}
|
||||
~CouchbaseRequest() { sharedDtor(); }
|
||||
CouchbaseRequest(const CouchbaseRequest& from)
|
||||
: NonreflectableMessage<CouchbaseRequest>() {
|
||||
metadata_tracking = &common_metadata_tracking;
|
||||
sharedCtor();
|
||||
MergeFrom(from);
|
||||
}
|
||||
|
||||
inline CouchbaseRequest& operator=(const CouchbaseRequest& from) {
|
||||
if (this != &from) {
|
||||
MergeFrom(from);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool selectBucketRequest(const butil::StringPiece& bucket_name);
|
||||
bool authenticateRequest(const butil::StringPiece& username,
|
||||
const butil::StringPiece& password);
|
||||
bool helloRequest();
|
||||
|
||||
// Using GetCollectionManifest instead of fetching collection ID directly
|
||||
// bool GetCollectionId(const butil::StringPiece& scope_name,
|
||||
// const butil::StringPiece& collection_name);
|
||||
|
||||
bool getScopeId(const butil::StringPiece& scope_name);
|
||||
|
||||
bool getCollectionManifest();
|
||||
|
||||
bool getLocalCachedCollectionId(const std::string& bucket, const std::string& scope,
|
||||
const std::string& collection, uint8_t* coll_id);
|
||||
|
||||
bool getCachedOrFetchCollectionId(
|
||||
std::string collection_name, uint8_t* coll_id,
|
||||
brpc::CouchbaseManifestManager* metadata_tracking,
|
||||
brpc::Channel* channel, const std::string& server,
|
||||
const std::string& selected_bucket,
|
||||
std::unordered_map<std::string, CouchbaseManifestManager::CollectionManifest>*
|
||||
local_cache);
|
||||
|
||||
// Collection-aware document operations
|
||||
bool getRequest(const butil::StringPiece& key,
|
||||
std::string collection_name = "_default",
|
||||
brpc::Channel* channel = nullptr, const std::string& server = "",
|
||||
const std::string& bucket = "");
|
||||
|
||||
bool upsertRequest(const butil::StringPiece& key,
|
||||
const butil::StringPiece& value, uint32_t flags,
|
||||
uint32_t exptime, uint64_t cas_value,
|
||||
std::string collection_name = "_default",
|
||||
brpc::Channel* channel = nullptr,
|
||||
const std::string& server = "", const std::string& bucket = "");
|
||||
|
||||
bool addRequest(const butil::StringPiece& key,
|
||||
const butil::StringPiece& value, uint32_t flags,
|
||||
uint32_t exptime, uint64_t cas_value,
|
||||
std::string collection_name = "_default",
|
||||
brpc::Channel* channel = nullptr, const std::string& server = "",
|
||||
const std::string& bucket = "");
|
||||
|
||||
bool appendRequest(const butil::StringPiece& key,
|
||||
const butil::StringPiece& value, uint32_t flags,
|
||||
uint32_t exptime, uint64_t cas_value,
|
||||
std::string collection_name = "_default",
|
||||
brpc::Channel* channel = nullptr,
|
||||
const std::string& server = "", const std::string& bucket = "");
|
||||
|
||||
bool prependRequest(const butil::StringPiece& key,
|
||||
const butil::StringPiece& value, uint32_t flags,
|
||||
uint32_t exptime, uint64_t cas_value,
|
||||
std::string collection_name = "_default",
|
||||
brpc::Channel* channel = nullptr,
|
||||
const std::string& server = "", const std::string& bucket = "");
|
||||
|
||||
bool deleteRequest(const butil::StringPiece& key,
|
||||
std::string collection_name = "_default",
|
||||
brpc::Channel* channel = nullptr,
|
||||
const std::string& server = "", const std::string& bucket = "");
|
||||
|
||||
bool versionRequest();
|
||||
|
||||
int pipelinedCount() const { return _pipelined_count; }
|
||||
|
||||
butil::IOBuf& rawBuffer() { return _buf; }
|
||||
const butil::IOBuf& rawBuffer() const {
|
||||
return _buf;
|
||||
} // used in couchbase_protocol serialization.
|
||||
void Swap(CouchbaseRequest* other);
|
||||
void MergeFrom(const CouchbaseRequest& from) override;
|
||||
void Clear() override;
|
||||
bool IsInitialized() const PB_527_OVERRIDE;
|
||||
};
|
||||
|
||||
class CouchbaseResponse : public NonreflectableMessage<CouchbaseResponse> {
|
||||
public:
|
||||
static brpc::CouchbaseManifestManager* metadata_tracking;
|
||||
|
||||
private:
|
||||
std::string _err;
|
||||
butil::IOBuf _buf;
|
||||
mutable int _cached_size_;
|
||||
bool popCounter(uint8_t command, uint64_t* new_value, uint64_t* cas_value);
|
||||
bool popStore(uint8_t command, uint64_t* cas_value);
|
||||
|
||||
void sharedCtor();
|
||||
void sharedDtor();
|
||||
void setCachedSize(int size) const;
|
||||
|
||||
public:
|
||||
uint16_t _status_code;
|
||||
|
||||
CouchbaseResponse() : NonreflectableMessage<CouchbaseResponse>() {
|
||||
sharedCtor();
|
||||
}
|
||||
~CouchbaseResponse() { sharedDtor(); }
|
||||
CouchbaseResponse(const CouchbaseResponse& from)
|
||||
: NonreflectableMessage<CouchbaseResponse>() {
|
||||
metadata_tracking = &common_metadata_tracking;
|
||||
sharedCtor();
|
||||
MergeFrom(from);
|
||||
}
|
||||
inline CouchbaseResponse& operator=(const CouchbaseResponse& from) {
|
||||
if (this != &from) {
|
||||
MergeFrom(from);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// the status codes are from Couchbase Binary Protocol documentation,
|
||||
// for original reference of status codes visit
|
||||
// https://github.com/couchbase/kv_engine/blob/master/include/mcbp/protocol/status.h
|
||||
enum Status {
|
||||
STATUS_SUCCESS = 0x00,
|
||||
STATUS_KEY_ENOENT = 0x01,
|
||||
STATUS_KEY_EEXISTS = 0x02,
|
||||
STATUS_E2BIG = 0x03,
|
||||
STATUS_EINVAL = 0x04,
|
||||
STATUS_NOT_STORED = 0x05,
|
||||
STATUS_DELTA_BADVAL = 0x06,
|
||||
STATUS_VBUCKET_BELONGS_TO_ANOTHER_SERVER = 0x07,
|
||||
STATUS_AUTH_ERROR = 0x20,
|
||||
STATUS_AUTH_CONTINUE = 0x21,
|
||||
STATUS_ERANGE = 0x22,
|
||||
STATUS_ROLLBACK = 0x23,
|
||||
STATUS_EACCESS = 0x24,
|
||||
STATUS_NOT_INITIALIZED = 0x25,
|
||||
STATUS_UNKNOWN_COMMAND = 0x81,
|
||||
STATUS_ENOMEM = 0x82,
|
||||
STATUS_NOT_SUPPORTED = 0x83,
|
||||
STATUS_EINTERNAL = 0x84,
|
||||
STATUS_EBUSY = 0x85,
|
||||
STATUS_ETMPFAIL = 0x86,
|
||||
STATUS_UNKNOWN_COLLECTION = 0x88,
|
||||
STATUS_NO_COLLECTIONS_MANIFEST = 0x89,
|
||||
STATUS_CANNOT_APPLY_COLLECTIONS_MANIFEST = 0x8a,
|
||||
STATUS_COLLECTIONS_MANIFEST_IS_AHEAD = 0x8b,
|
||||
STATUS_UNKNOWN_SCOPE = 0x8c,
|
||||
STATUS_DCP_STREAM_ID_INVALID = 0x8d,
|
||||
STATUS_DURABILITY_INVALID_LEVEL = 0xa0,
|
||||
STATUS_DURABILITY_IMPOSSIBLE = 0xa1,
|
||||
STATUS_SYNC_WRITE_IN_PROGRESS = 0xa2,
|
||||
STATUS_SYNC_WRITE_AMBIGUOUS = 0xa3,
|
||||
STATUS_SYNC_WRITE_RE_COMMIT_IN_PROGRESS = 0xa4,
|
||||
STATUS_SUBDOC_PATH_NOT_FOUND = 0xc0,
|
||||
STATUS_SUBDOC_PATH_MISMATCH = 0xc1,
|
||||
STATUS_SUBDOC_PATH_EINVAL = 0xc2,
|
||||
STATUS_SUBDOC_PATH_E2BIG = 0xc3,
|
||||
STATUS_SUBDOC_DOC_E2DEEP = 0xc4,
|
||||
STATUS_SUBDOC_VALUE_CANTINSERT = 0xc5,
|
||||
STATUS_SUBDOC_DOC_NOT_JSON = 0xc6,
|
||||
STATUS_SUBDOC_NUM_E2BIG = 0xc7,
|
||||
STATUS_SUBDOC_DELTA_E2BIG = 0xc8,
|
||||
STATUS_SUBDOC_PATH_EEXISTS = 0xc9,
|
||||
STATUS_SUBDOC_VALUE_E2DEEP = 0xca,
|
||||
STATUS_SUBDOC_INVALID_COMBO = 0xcb,
|
||||
STATUS_SUBDOC_MULTI_PATH_FAILURE = 0xcc,
|
||||
STATUS_SUBDOC_SUCCESS_DELETED = 0xcd,
|
||||
STATUS_SUBDOC_XATTR_INVALID_FLAG_COMBO = 0xce,
|
||||
STATUS_SUBDOC_XATTR_INVALID_KEY_COMBO = 0xcf,
|
||||
STATUS_SUBDOC_XATTR_UNKNOWN_MACRO = 0xd0,
|
||||
STATUS_SUBDOC_XATTR_UNKNOWN_VATTR = 0xd1,
|
||||
STATUS_SUBDOC_XATTR_CANT_MODIFY_VATTR = 0xd2,
|
||||
STATUS_SUBDOC_MULTI_PATH_FAILURE_DELETED = 0xd3,
|
||||
STATUS_SUBDOC_INVALID_XATTR_ORDER = 0xd4,
|
||||
STATUS_SUBDOC_XATTR_UNKNOWN_VATTR_MACRO = 0xd5,
|
||||
STATUS_SUBDOC_CAN_ONLY_REVIVE_DELETED_DOCUMENTS = 0xd6,
|
||||
STATUS_SUBDOC_DELETED_DOCUMENT_CANT_HAVE_VALUE = 0xd7,
|
||||
STATUS_XATTR_EINVAL = 0xe0
|
||||
};
|
||||
const char* couchbaseBinaryCommandToString(uint8_t cmd);
|
||||
void MergeFrom(const CouchbaseResponse& from) override;
|
||||
void Clear() override;
|
||||
bool IsInitialized() const PB_527_OVERRIDE;
|
||||
|
||||
butil::IOBuf& rawBuffer() { return _buf; }
|
||||
static const char* statusStr(Status);
|
||||
|
||||
// Helper method to format error messages with status codes
|
||||
static std::string formatErrorMessage(uint16_t status_code,
|
||||
const std::string& operation,
|
||||
const std::string& error_msg = "");
|
||||
|
||||
// Add methods to handle response parsing
|
||||
void swap(CouchbaseResponse* other);
|
||||
bool popGet(butil::IOBuf* value, uint32_t* flags, uint64_t* cas_value);
|
||||
bool popGet(std::string* value, uint32_t* flags, uint64_t* cas_value);
|
||||
const std::string& lastError() const { return _err; }
|
||||
bool popUpsert(uint64_t* cas_value);
|
||||
bool popAdd(uint64_t* cas_value);
|
||||
// Warning: Not tested
|
||||
// bool popReplace(uint64_t* cas_value);
|
||||
bool popAppend(uint64_t* cas_value);
|
||||
bool popPrepend(uint64_t* cas_value);
|
||||
bool popSelectBucket(uint64_t* cas_value);
|
||||
bool popAuthenticate(uint64_t* cas_value);
|
||||
bool popHello(uint64_t* cas_value);
|
||||
|
||||
// Collection-related response methods
|
||||
bool popCollectionId(uint8_t* collection_id);
|
||||
|
||||
bool popManifest(std::string* manifest_json);
|
||||
|
||||
bool popDelete();
|
||||
// Warning: Not tested
|
||||
// bool popFlush();
|
||||
// bool popIncrement(uint64_t* new_value, uint64_t* cas_value);
|
||||
// bool popDecrement(uint64_t* new_value, uint64_t* cas_value);
|
||||
// bool popTouch();
|
||||
bool popVersion(std::string* version);
|
||||
};
|
||||
|
||||
friend bool sendRequest(CouchbaseOperations::operation_type op_type,
|
||||
const std::string& key, const std::string& value,
|
||||
std::string collection_name,
|
||||
CouchbaseOperations::Result* result,
|
||||
brpc::Channel* channel, const std::string& server,
|
||||
const std::string& bucket, CouchbaseRequest* request,
|
||||
CouchbaseResponse* response);
|
||||
|
||||
// Pipeline management - per instance
|
||||
std::queue<operation_type> pipeline_operations_queue;
|
||||
CouchbaseRequest pipeline_request_couchbase_req;
|
||||
CouchbaseResponse pipeline_response_couchbase_resp;
|
||||
bool pipeline_active;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
@@ -0,0 +1,50 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_DATA_FACTORY_H
|
||||
#define BRPC_DATA_FACTORY_H
|
||||
|
||||
// To brpc developers: This is a header included by user, don't depend
|
||||
// on internal structures, use opaque pointers instead.
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// ---- thread safety ----
|
||||
// Method implementations of this interface should be thread-safe
|
||||
class DataFactory {
|
||||
public:
|
||||
virtual ~DataFactory() {}
|
||||
|
||||
// Implement this method to create a piece of data
|
||||
// Returns the data, NULL on error.
|
||||
virtual void* CreateData() const = 0;
|
||||
|
||||
// Implement this method to destroy data created by Create().
|
||||
virtual void DestroyData(void*) const = 0;
|
||||
|
||||
// Overwrite this method to reset the data before reuse. Nothing done by default.
|
||||
// Returns
|
||||
// true: the data can be kept for future reuse
|
||||
// false: the data is improper to be reused and should be sent to
|
||||
// DestroyData() immediately after calling this method
|
||||
virtual bool ResetData(void*) const { return true; }
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif // BRPC_DATA_FACTORY_H
|
||||
@@ -0,0 +1,114 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
||||
#ifndef BRPC_DESCRIBABLE_H
|
||||
#define BRPC_DESCRIBABLE_H
|
||||
|
||||
#include <ostream>
|
||||
#include "butil/macros.h"
|
||||
#include "butil/class_name.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
struct DescribeOptions {
|
||||
DescribeOptions()
|
||||
: verbose(true)
|
||||
, use_html(false)
|
||||
{}
|
||||
|
||||
bool verbose;
|
||||
bool use_html;
|
||||
};
|
||||
|
||||
class Describable {
|
||||
public:
|
||||
virtual ~Describable() {}
|
||||
virtual void Describe(std::ostream& os, const DescribeOptions&) const {
|
||||
os << butil::class_name_str(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class NonConstDescribable {
|
||||
public:
|
||||
virtual ~NonConstDescribable() {}
|
||||
virtual void Describe(std::ostream& os, const DescribeOptions&) {
|
||||
os << butil::class_name_str(*this);
|
||||
}
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const Describable& obj) {
|
||||
DescribeOptions options;
|
||||
options.verbose = false;
|
||||
obj.Describe(os, options);
|
||||
return os;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os,
|
||||
NonConstDescribable& obj) {
|
||||
DescribeOptions options;
|
||||
options.verbose = false;
|
||||
obj.Describe(os, options);
|
||||
return os;
|
||||
}
|
||||
|
||||
// Append `indent' spaces after each newline.
|
||||
// Example:
|
||||
// IndentingOStream os1(std::cout, 2);
|
||||
// IndentingOStream os2(os1, 2);
|
||||
// std::cout << "begin1\nhello" << std::endl << "world\nend1" << std::endl;
|
||||
// os1 << "begin2\nhello" << std::endl << "world\nend2" << std::endl;
|
||||
// os2 << "begin3\nhello" << std::endl << "world\nend3" << std::endl;
|
||||
// Output:
|
||||
// begin1
|
||||
// hello
|
||||
// world
|
||||
// end1
|
||||
// begin2
|
||||
// hello
|
||||
// world
|
||||
// end2
|
||||
// begin3
|
||||
// hello
|
||||
// world
|
||||
// end3
|
||||
class IndentingOStream : virtual private std::streambuf, public std::ostream {
|
||||
public:
|
||||
IndentingOStream(std::ostream& dest, int indent)
|
||||
: std::ostream(this)
|
||||
, _dest(dest.rdbuf())
|
||||
, _is_at_start_of_line(false)
|
||||
, _indent(indent, ' ')
|
||||
{}
|
||||
protected:
|
||||
int overflow(int ch) override {
|
||||
if (_is_at_start_of_line && ch != '\n' ) {
|
||||
_dest->sputn(_indent.data(), _indent.size());
|
||||
}
|
||||
_is_at_start_of_line = (ch == '\n');
|
||||
return _dest->sputc(ch);
|
||||
}
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(IndentingOStream);
|
||||
std::streambuf* _dest;
|
||||
bool _is_at_start_of_line;
|
||||
std::string _indent;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif // BRPC_DESCRIBABLE_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user