chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# 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.
|
||||
|
||||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/output/bin)
|
||||
|
||||
add_subdirectory(parallel_http)
|
||||
add_subdirectory(rpc_press)
|
||||
add_subdirectory(rpc_replay)
|
||||
add_subdirectory(rpc_view)
|
||||
add_subdirectory(trackme_server)
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 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 'syntax="proto2";' to the beginning of all proto files under
|
||||
# PWD(recursively) if the proto file does not have it.
|
||||
for file in $(find . -name "*.proto"); do
|
||||
if grep -q 'syntax\s*=\s*"proto2";' $file; then
|
||||
echo "[already had] $file";
|
||||
else
|
||||
sed -i '1s/^/syntax="proto2";\n/' $file
|
||||
echo "[replaced] $file"
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Bthread Stack Print Tool
|
||||
|
||||
this only for running process, core dump is not supported.
|
||||
|
||||
Get Started:
|
||||
1. gdb attach <pid>
|
||||
2. source gdb_bthread_stack.py
|
||||
3. bthread_begin
|
||||
4. bthread_list
|
||||
5. bthread_frame 0
|
||||
6. bt / up / down
|
||||
7. bthread_end
|
||||
|
||||
Commands:
|
||||
1. bthread_num: print all bthread nums
|
||||
2. bthread_begin <num>: enter bthread debug mode, `num` is max scanned bthreads, default will scan all
|
||||
3. bthread_list: list all bthreads
|
||||
4. bthread_frame <id>: switch stack to bthread, id will displayed in bthread_list
|
||||
5. bthread_meta <id>: print bthread meta
|
||||
6. bthread_reg_restore: bthread_frame will modify registers, reg_restore will restore them
|
||||
7. bthread_end: exit bthread debug mode
|
||||
8. bthread_regs <id>: print bthread registers
|
||||
9. bthread_all: print all bthread frames
|
||||
|
||||
when call bthread_frame, registers will be modified,
|
||||
remember to call bthread_end after debug, or process will be corrupted
|
||||
|
||||
after call bthread_frame, you can call `bt`/`up`/`down`, or other gdb command
|
||||
"""
|
||||
|
||||
import gdb
|
||||
|
||||
bthreads = []
|
||||
status = False
|
||||
|
||||
def get_bthread_num():
|
||||
root_agent = gdb.parse_and_eval("&(((*(((*bthread::g_task_control)._nbthreads)._combiner._M_ptr))._agents).root_)")
|
||||
global_res = int(gdb.parse_and_eval("(*(((*bthread::g_task_control)._nbthreads)._combiner._M_ptr))._global_result"))
|
||||
get_agent = "(*(('bvar::detail::AgentCombiner<long, long, bvar::detail::AddTo<long> >::Agent' *){}))"
|
||||
last_node = root_agent
|
||||
long_type = gdb.lookup_type("long")
|
||||
while True:
|
||||
agent = gdb.parse_and_eval(get_agent.format(last_node))
|
||||
if last_node != root_agent:
|
||||
val = int(agent["element"]["_value"].cast(long_type))
|
||||
gdb.parse_and_eval(get_agent.format(last_node))
|
||||
global_res += val
|
||||
if agent["next_"] == root_agent:
|
||||
return global_res
|
||||
last_node = agent["next_"]
|
||||
|
||||
def get_all_bthreads(total):
|
||||
global bthreads
|
||||
bthreads = []
|
||||
count = 0
|
||||
groups = int(gdb.parse_and_eval("(size_t)'butil::ResourcePool<bthread::TaskMeta>::_ngroup'"))
|
||||
for group in range(groups):
|
||||
blocks = int(gdb.parse_and_eval("(unsigned long)(*((*((('butil::static_atomic<butil::ResourcePool<bthread::TaskMeta>::BlockGroup*>' *)('butil::ResourcePool<bthread::TaskMeta>::_block_groups')) + {})).val)).nblock".format(group)))
|
||||
for block in range(blocks):
|
||||
items = int(gdb.parse_and_eval("(*(*(('butil::ResourcePool<bthread::TaskMeta>::Block' **)((*((*((('butil::static_atomic<butil::ResourcePool<bthread::TaskMeta>::BlockGroup*>' *)('butil::ResourcePool<bthread::TaskMeta>::_block_groups'))+ {})).val)).blocks) + {}))).nitem".format(group, block)))
|
||||
for item in range(items):
|
||||
task_meta = gdb.parse_and_eval("*(('bthread::TaskMeta' *)((*(*(('butil::ResourcePool<bthread::TaskMeta>::Block' **)((*((*((('butil::static_atomic<butil::ResourcePool<bthread::TaskMeta>::BlockGroup*>' *)('butil::ResourcePool<bthread::TaskMeta>::_block_groups')) + {})).val)).blocks) + {}))).items) + {})".format(group, block, item))
|
||||
version_tid = (int(task_meta["tid"]) >> 32)
|
||||
version_butex = gdb.parse_and_eval(
|
||||
"*(uint32_t *){}".format(task_meta["version_butex"]))
|
||||
if version_tid == int(version_butex) and int(task_meta["attr"]["stack_type"]) != 0:
|
||||
bthreads.append(task_meta)
|
||||
count += 1
|
||||
if count >= total:
|
||||
return
|
||||
|
||||
class BthreadListCmd(gdb.Command):
|
||||
"""list all bthreads, print format is 'id\ttid\tfunction\thas stack'"""
|
||||
def __init__(self):
|
||||
gdb.Command.__init__(self, "bthread_list", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
|
||||
|
||||
def invoke(self, arg, tty):
|
||||
global status
|
||||
global bthreads
|
||||
if not status:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
print("id\t\ttid\t\tfunction\t\thas stack\t\t\ttotal:{}".format(len(bthreads)))
|
||||
for i, t in enumerate(bthreads):
|
||||
print("#{}\t\t{}\t\t{}\t\t{}".format(i, t["tid"], t["fn"], "no" if str(t["stack"]) == "0x0" else "yes"))
|
||||
|
||||
class BthreadNumCmd(gdb.Command):
|
||||
"""list active bthreads num"""
|
||||
def __init__(self):
|
||||
gdb.Command.__init__(self, "bthread_num", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
|
||||
|
||||
def invoke(self, arg, tty):
|
||||
res = get_bthread_num()
|
||||
print(res)
|
||||
|
||||
class BthreadFrameCmd(gdb.Command):
|
||||
"""bthread_frame <id>, select bthread frame by id"""
|
||||
def __init__(self):
|
||||
gdb.Command.__init__(self, "bthread_frame", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
|
||||
|
||||
def invoke(self, arg, tty):
|
||||
global status
|
||||
global bthreads
|
||||
if not status:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
if not arg:
|
||||
print("bthread_frame <id>, see 'bthread_list'")
|
||||
return
|
||||
bthread_id = int(arg)
|
||||
if bthread_id >= len(bthreads):
|
||||
print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads)))
|
||||
return
|
||||
stack = bthreads[bthread_id]["stack"]
|
||||
if str(stack) == "0x0":
|
||||
print("this bthread has no stack")
|
||||
return
|
||||
context = gdb.parse_and_eval("(*(('bthread::ContextualStack' *){})).context".format(stack))
|
||||
rip = gdb.parse_and_eval("*(uint64_t*)({}+7*8)".format(context))
|
||||
rbp = gdb.parse_and_eval("*(uint64_t*)({}+6*8)".format(context))
|
||||
rsp = gdb.parse_and_eval("{}+8*8".format(context))
|
||||
gdb.parse_and_eval("$rip = {}".format(rip))
|
||||
gdb.parse_and_eval("$rsp = {}".format(rsp))
|
||||
gdb.parse_and_eval("$rbp = {}".format(rbp))
|
||||
|
||||
class BthreadAllCmd(gdb.Command):
|
||||
"""print all bthread frames"""
|
||||
def __init__(self):
|
||||
gdb.Command.__init__(self, "bthread_all", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
|
||||
|
||||
def invoke(self, arg, tty):
|
||||
global status
|
||||
global bthreads
|
||||
if not status:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
for bthread_id in range(len(bthreads)):
|
||||
stack = bthreads[bthread_id]["stack"]
|
||||
if str(stack) == "0x0":
|
||||
print("this bthread has no stack")
|
||||
continue
|
||||
try:
|
||||
context = gdb.parse_and_eval("(*(('bthread::ContextualStack' *){})).context".format(stack))
|
||||
rip = gdb.parse_and_eval("*(uint64_t*)({}+7*8)".format(context))
|
||||
rbp = gdb.parse_and_eval("*(uint64_t*)({}+6*8)".format(context))
|
||||
rsp = gdb.parse_and_eval("{}+8*8".format(context))
|
||||
gdb.parse_and_eval("$rip = {}".format(rip))
|
||||
gdb.parse_and_eval("$rsp = {}".format(rsp))
|
||||
gdb.parse_and_eval("$rbp = {}".format(rbp))
|
||||
print("Bthread {}:".format(bthread_id))
|
||||
gdb.execute('bt')
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
class BthreadRegsCmd(gdb.Command):
|
||||
"""bthread_regs <id>, print bthread registers"""
|
||||
def __init__(self):
|
||||
gdb.Command.__init__(self, "bthread_regs", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
|
||||
|
||||
def invoke(self, arg, tty):
|
||||
global status
|
||||
global bthreads
|
||||
if not status:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
if not arg:
|
||||
print("bthread_regs <id>, see 'bthread_list'")
|
||||
return
|
||||
bthread_id = int(arg)
|
||||
if bthread_id >= len(bthreads):
|
||||
print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads)))
|
||||
return
|
||||
stack = bthreads[bthread_id]["stack"]
|
||||
if str(stack) == "0x0":
|
||||
print("this bthread has no stack")
|
||||
return
|
||||
context = gdb.parse_and_eval("(*(('bthread::ContextualStack' *){})).context".format(stack))
|
||||
rip = int(gdb.parse_and_eval("*(uint64_t*)({}+7*8)".format(context)))
|
||||
rbp = int(gdb.parse_and_eval("*(uint64_t*)({}+6*8)".format(context)))
|
||||
rbx = int(gdb.parse_and_eval("*(uint64_t*)({}+5*8)".format(context)))
|
||||
r15 = int(gdb.parse_and_eval("*(uint64_t*)({}+4*8)".format(context)))
|
||||
r14 = int(gdb.parse_and_eval("*(uint64_t*)({}+3*8)".format(context)))
|
||||
r13 = int(gdb.parse_and_eval("*(uint64_t*)({}+2*8)".format(context)))
|
||||
r12 = int(gdb.parse_and_eval("*(uint64_t*)({}+1*8)".format(context)))
|
||||
rsp = int(gdb.parse_and_eval("{}+8*8".format(context)))
|
||||
print("rip: 0x{:x}\nrsp: 0x{:x}\nrbp: 0x{:x}\nrbx: 0x{:x}\nr15: 0x{:x}\nr14: 0x{:x}\nr13: 0x{:x}\nr12: 0x{:x}".format(rip, rsp, rbp, rbx, r15, r14, r13, r12))
|
||||
|
||||
class BthreadMetaCmd(gdb.Command):
|
||||
"""bthread_meta <id>, print task meta by id"""
|
||||
def __init__(self):
|
||||
gdb.Command.__init__(self, "bthread_meta", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
|
||||
|
||||
def invoke(self, arg, tty):
|
||||
global status
|
||||
global bthreads
|
||||
if not status:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
if not arg:
|
||||
print("bthread_meta <id>, see 'bthread_list'")
|
||||
return
|
||||
bthread_id = int(arg)
|
||||
if bthread_id >= len(bthreads):
|
||||
print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads)))
|
||||
return
|
||||
print(bthreads[bthread_id])
|
||||
|
||||
class BthreadBeginCmd(gdb.Command):
|
||||
"""enter bthread debug mode"""
|
||||
def __init__(self):
|
||||
gdb.Command.__init__(self, "bthread_begin", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
|
||||
|
||||
def invoke(self, arg, tty):
|
||||
global status
|
||||
if status:
|
||||
print("Already in bthread debug mode, do not switch thread before exec 'bthread_end' !!!")
|
||||
return
|
||||
active_bthreads = get_bthread_num()
|
||||
scanned_bthreads = active_bthreads
|
||||
if arg:
|
||||
num_arg = int(arg)
|
||||
if num_arg < active_bthreads:
|
||||
scanned_bthreads = num_arg
|
||||
else:
|
||||
print("requested bthreads {} more than actived, will display {} bthreads".format(num_arg, scanned_bthreads))
|
||||
print("Active bthreads: {}, will display {} bthreads".format(active_bthreads, scanned_bthreads))
|
||||
get_all_bthreads(scanned_bthreads)
|
||||
gdb.parse_and_eval("$saved_rip = $rip")
|
||||
gdb.parse_and_eval("$saved_rsp = $rsp")
|
||||
gdb.parse_and_eval("$saved_rbp = $rbp")
|
||||
status = True
|
||||
print("Enter bthread debug mode, do not switch thread before exec 'bthread_end' !!!")
|
||||
|
||||
class BthreadRegRestoreCmd(gdb.Command):
|
||||
"""restore registers"""
|
||||
def __init__(self):
|
||||
gdb.Command.__init__(self, "bthread_reg_restore", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
|
||||
|
||||
def invoke(self, arg, tty):
|
||||
global status
|
||||
if not status:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
gdb.parse_and_eval("$rip = $saved_rip")
|
||||
gdb.parse_and_eval("$rsp = $saved_rsp")
|
||||
gdb.parse_and_eval("$rbp = $saved_rbp")
|
||||
print("OK")
|
||||
|
||||
class BthreadEndCmd(gdb.Command):
|
||||
"""exit bthread debug mode"""
|
||||
def __init__(self):
|
||||
gdb.Command.__init__(self, "bthread_end", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
|
||||
|
||||
def invoke(self, arg, tty):
|
||||
global status
|
||||
if not status:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
gdb.parse_and_eval("$rip = $saved_rip")
|
||||
gdb.parse_and_eval("$rsp = $saved_rsp")
|
||||
gdb.parse_and_eval("$rbp = $saved_rbp")
|
||||
status = False
|
||||
print("Exit bthread debug mode")
|
||||
|
||||
BthreadListCmd()
|
||||
BthreadNumCmd()
|
||||
BthreadBeginCmd()
|
||||
BthreadEndCmd()
|
||||
BthreadFrameCmd()
|
||||
BthreadAllCmd()
|
||||
BthreadMetaCmd()
|
||||
BthreadRegRestoreCmd()
|
||||
BthreadRegsCmd()
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 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.
|
||||
|
||||
output=$(cat $1/RELEASE_VERSION)
|
||||
abbr_commit_hash=$(git log -1 --format="%h" 2> /dev/null)
|
||||
committer_date=($(git log -1 --format="%ci" 2> /dev/null))
|
||||
committer_date="${committer_date[0]}T${committer_date[1]}${committer_date[2]:0:3}:${committer_date[2]:3:2}"
|
||||
branch=$(git rev-parse --abbrev-ref HEAD 2> /dev/null)
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
output=$output"\\|"$branch"\\|"$abbr_commit_hash"\\|"$committer_date
|
||||
fi
|
||||
echo $output
|
||||
@@ -0,0 +1,347 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Bthread Stack Print Tool
|
||||
|
||||
this only for running process, core dump is not supported.
|
||||
|
||||
Get Started:
|
||||
1. lldb attach -p <pid>
|
||||
2. command script import lldb_bthread_stack.py
|
||||
3. bthread_begin
|
||||
4. bthread_list
|
||||
5. bthread_frame 0
|
||||
6. bt / up / down
|
||||
7. bthread_end
|
||||
|
||||
Commands:
|
||||
1. bthread_num: print all bthread nums
|
||||
2. bthread_begin <num>: enter bthread debug mode, `num` is max scanned bthreads, default will scan all
|
||||
3. bthread_list: list all bthreads
|
||||
4. bthread_frame <id>: switch stack to bthread, id will displayed in bthread_list
|
||||
5. bthread_meta <id>: print bthread meta
|
||||
6. bthread_reg_restore: bthread_frame will modify registers, reg_restore will restore them
|
||||
7. bthread_end: exit bthread debug mode
|
||||
8. bthread_regs <id>: print bthread registers
|
||||
9. bthread_all: print all bthread frames
|
||||
|
||||
when call bthread_frame, registers will be modified,
|
||||
remember to call bthread_end after debug, or process will be corrupted
|
||||
|
||||
after call bthread_frame, you can call `bt`/`up`/`down`, or other gdb command
|
||||
"""
|
||||
|
||||
import lldb
|
||||
|
||||
|
||||
class GlobalState():
|
||||
def __init__(self):
|
||||
self.started: bool = False
|
||||
self.bthreads: list = []
|
||||
self.saved_regs: dict = {}
|
||||
|
||||
def reset(self) -> None:
|
||||
self.started = False
|
||||
self.bthreads.clear()
|
||||
|
||||
def get_bthread(self, idx_str: str) -> lldb.SBValue:
|
||||
if not self.started:
|
||||
print("Not in bthread debug mode")
|
||||
return None
|
||||
if len(idx_str) == 0:
|
||||
print("bthread_frame <id>, see 'bthread_list'")
|
||||
try:
|
||||
bthread_idx = int(idx_str)
|
||||
except ValueError:
|
||||
print("please input a valid interger.")
|
||||
return None
|
||||
|
||||
if bthread_idx >= len(self.bthreads):
|
||||
print("id {} exceeds max bthread nums {}".format(
|
||||
bthread_idx, len(self.bthreads)))
|
||||
return None
|
||||
return self.bthreads[bthread_idx]
|
||||
|
||||
|
||||
global_state = GlobalState()
|
||||
|
||||
|
||||
def get_child(value: lldb.SBValue, childs_value_name: str) -> lldb.SBValue:
|
||||
r"""get child value by value name str split by '.'"""
|
||||
result = value
|
||||
childs_value_list = childs_value_name.split('.')
|
||||
for child_value_name in childs_value_list:
|
||||
result = result.GetChildMemberWithName(child_value_name)
|
||||
return result
|
||||
|
||||
|
||||
def find_global_value(target: lldb.SBTarget, value_name: str) -> lldb.SBValue:
|
||||
r""" find global value by value name"""
|
||||
name_list = value_name.split('.')
|
||||
root_name = name_list[0]
|
||||
root_value = target.FindGlobalVariables(
|
||||
root_name, 1, lldb.eMatchTypeNormal)[0]
|
||||
if (len(name_list) == 1):
|
||||
return root_value
|
||||
return get_child(root_value, '.'.join(name_list[1:]))
|
||||
|
||||
|
||||
def get_bthreads_num(target: lldb.SBTarget):
|
||||
root_agent = find_global_value(
|
||||
target, "bthread::g_task_control._nbthreads._combiner._agents.root_")
|
||||
global_res = find_global_value(
|
||||
target, "bthread::g_task_control._nbthreads._combiner._global_result").GetValueAsSigned()
|
||||
long_type = target.GetBasicType(lldb.eBasicTypeLong)
|
||||
|
||||
last_node = root_agent
|
||||
# agent_type: bvar::detail::AgentCombiner<long, long, bvar::detail::AddTo<long> >::Agent>
|
||||
agent_type: lldb.SBType = last_node.GetType().GetTemplateArgumentType(0)
|
||||
while True:
|
||||
agent = last_node.Cast(agent_type)
|
||||
if (last_node.GetLocation() != root_agent.GetLocation()):
|
||||
val = get_child(agent, "element._value").Cast(
|
||||
long_type).GetValueAsSigned()
|
||||
global_res += val
|
||||
if (get_child(agent, "next_").Dereference().GetLocation() == root_agent.GetLocation()):
|
||||
return global_res
|
||||
last_node = get_child(agent, "next_").Dereference()
|
||||
|
||||
|
||||
def get_all_bthreads(target: lldb.SBTarget, total: int):
|
||||
bthreads = []
|
||||
groups = find_global_value(
|
||||
target, "butil::ResourcePool<bthread::TaskMeta>::_ngroup.val").GetValueAsUnsigned()
|
||||
long_type = target.GetBasicType(lldb.eBasicTypeLong)
|
||||
uint32_t_type = target.FindFirstType("uint32_t")
|
||||
block_groups = find_global_value(
|
||||
target, "butil::ResourcePool<bthread::TaskMeta>::_block_groups")
|
||||
for group in range(groups):
|
||||
block_group = get_child(
|
||||
block_groups.GetChildAtIndex(group), "val").Dereference()
|
||||
nblock = get_child(block_group, "nblock").Cast(
|
||||
long_type).GetValueAsUnsigned()
|
||||
blocks = get_child(block_group, "blocks")
|
||||
for block in range(nblock):
|
||||
# block_type: butil::ResourcePool<bthread::TaskMeta>::Block
|
||||
block_type = blocks.GetChildAtIndex(
|
||||
block).GetType().GetTemplateArgumentType(0)
|
||||
block = blocks.GetChildAtIndex(
|
||||
block).Cast(block_type).Dereference()
|
||||
nitem = get_child(block, "nitem").GetValueAsUnsigned()
|
||||
task_meta_array_type = target.FindFirstType(
|
||||
"bthread::TaskMeta").GetArrayType(nitem)
|
||||
tasks = get_child(block, "items").Cast(task_meta_array_type)
|
||||
for i in range(nitem):
|
||||
task_meta = tasks.GetChildAtIndex(i)
|
||||
version_tid = get_child(
|
||||
task_meta, "tid").GetValueAsUnsigned() >> 32
|
||||
version_butex = get_child(task_meta, "version_butex").Cast(
|
||||
uint32_t_type.GetPointerType()).Dereference().GetValueAsUnsigned()
|
||||
# stack_type: bthread::ContextualStack
|
||||
stack_type = get_child(
|
||||
task_meta, "attr.stack_type").GetValueAsUnsigned()
|
||||
if version_tid == version_butex and stack_type != 0:
|
||||
if len(bthreads) >= total:
|
||||
return bthreads
|
||||
bthreads.append(task_meta)
|
||||
return bthreads
|
||||
|
||||
# lldb bthread commands
|
||||
def bthread_begin(debugger, command, result, internal_dict):
|
||||
if global_state.started:
|
||||
print("Already in bthread debug mode, do not switch thread before exec 'bthread_end' !!!")
|
||||
return
|
||||
target = debugger.GetSelectedTarget()
|
||||
active_bthreads = get_bthreads_num(target)
|
||||
|
||||
if len(command) == 0:
|
||||
request_bthreds = active_bthreads
|
||||
else:
|
||||
try:
|
||||
request_bthreds = int(command)
|
||||
except ValueError:
|
||||
print("please input a valid interger.")
|
||||
return
|
||||
|
||||
scanned_bthreds = active_bthreads
|
||||
if request_bthreds > active_bthreads:
|
||||
print("requested bthreads {} more than actived, will display {} bthreads".format(
|
||||
request_bthreds, active_bthreads))
|
||||
else:
|
||||
scanned_bthreds = request_bthreds
|
||||
print("Active bthreads: {}, will display {} bthreads".format(
|
||||
active_bthreads, scanned_bthreds))
|
||||
global_state.bthreads = get_all_bthreads(target, scanned_bthreds)
|
||||
|
||||
# backup registers
|
||||
current_frame = target.GetProcess().GetSelectedThread().GetSelectedFrame()
|
||||
saved_regs = dict()
|
||||
saved_regs["rip"] = current_frame.FindRegister("rip").GetValueAsUnsigned()
|
||||
saved_regs["rsp"] = current_frame.FindRegister("rsp").GetValueAsUnsigned()
|
||||
saved_regs["rbp"] = current_frame.FindRegister("rbp").GetValueAsUnsigned()
|
||||
global_state.saved_regs = saved_regs
|
||||
|
||||
global_state.started = True
|
||||
print("Enter bthread debug mode, do not switch thread before exec 'bthread_end' !!!")
|
||||
|
||||
|
||||
def bthread_list(debugger, command, result, internal_dict):
|
||||
r"""list all bthreads, print format is 'id\ttid\tfunction\thas stack'"""
|
||||
if not global_state.started:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
|
||||
print("id\t\ttid\t\tfunction\t\t\t\thas stack\t\t\ttotal:{}".format(
|
||||
len(global_state.bthreads)))
|
||||
for i, t in enumerate(global_state.bthreads):
|
||||
tid = get_child(t, "tid").GetValueAsUnsigned()
|
||||
fn = get_child(t, "fn")
|
||||
has_stack = get_child(t, "stack").GetLocation() == "0x0"
|
||||
print("#{}\t\t{}\t\t{}\t\t{}".format(
|
||||
i, tid, fn, "no" if has_stack else "yes"))
|
||||
|
||||
|
||||
def bthread_num(debugger, command, result, internal_dict):
|
||||
r"""list active bthreads num"""
|
||||
if not global_state.started:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
|
||||
target = debugger.GetSelectedTarget()
|
||||
active_bthreads = get_bthreads_num(target)
|
||||
print(active_bthreads)
|
||||
|
||||
|
||||
def bthread_frame(debugger, command, result, internal_dict):
|
||||
r"""bthread_frame <id>, select bthread frame by id"""
|
||||
bthread = global_state.get_bthread(command)
|
||||
if bthread is None:
|
||||
return
|
||||
|
||||
stack = bthread.GetChildMemberWithName("stack")
|
||||
context = stack.Dereference().GetChildMemberWithName("context")
|
||||
|
||||
target = debugger.GetSelectedTarget()
|
||||
uint64_t_type = target.FindFirstType("uint64_t")
|
||||
target = debugger.GetSelectedTarget()
|
||||
|
||||
rip = target.CreateValueFromAddress("rip", lldb.SBAddress(
|
||||
context.GetValueAsUnsigned() + 7*8, target), uint64_t_type).GetValueAsUnsigned()
|
||||
rbp = target.CreateValueFromAddress("rbp", lldb.SBAddress(
|
||||
context.GetValueAsUnsigned() + 6*8, target), uint64_t_type).GetValueAsUnsigned()
|
||||
rsp = context.GetValueAsUnsigned() + 8*8
|
||||
|
||||
debugger.HandleCommand(f"register write rip {rip}")
|
||||
debugger.HandleCommand(f"register write rbp {rbp}")
|
||||
debugger.HandleCommand(f"register write rsp {rsp}")
|
||||
|
||||
|
||||
def bthread_all(debugger, command, result, internal_dict):
|
||||
r"""print all bthread frames"""
|
||||
if not global_state.started:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
|
||||
bthreads = global_state.bthreads
|
||||
bthread_num = len(bthreads)
|
||||
for i in range(bthread_num):
|
||||
bthread_frame(debugger, str(i), result, internal_dict)
|
||||
debugger.HandleCommand("bt")
|
||||
|
||||
|
||||
def bthread_meta(debugger, command, result, internal_dict):
|
||||
r"""bthread_meta <id>, print task meta by id"""
|
||||
bthread = global_state.get_bthread(command)
|
||||
if bthread is None:
|
||||
return
|
||||
print(bthread)
|
||||
|
||||
|
||||
def bthread_regs(debugger, command, result, internal_dict):
|
||||
r"""bthread_regs <id>, print bthread registers"""
|
||||
bthread = global_state.get_bthread(command)
|
||||
if bthread is None:
|
||||
return
|
||||
target = debugger.GetSelectedTarget()
|
||||
stack = get_child(bthread, "stack").Dereference()
|
||||
context = get_child(stack, "context")
|
||||
ctx_addr = context.GetValueAsUnsigned()
|
||||
uint64_t_type = target.FindFirstType("uint64_t")
|
||||
|
||||
rip = target.CreateValueFromAddress("rip", lldb.SBAddress(
|
||||
ctx_addr + 7*8, target), uint64_t_type).GetValueAsUnsigned()
|
||||
rbp = target.CreateValueFromAddress("rbp", lldb.SBAddress(
|
||||
ctx_addr + 6*8, target), uint64_t_type).GetValueAsUnsigned()
|
||||
rbx = target.CreateValueFromAddress("rbx", lldb.SBAddress(
|
||||
ctx_addr + 5*8, target), uint64_t_type).GetValueAsUnsigned()
|
||||
r15 = target.CreateValueFromAddress("r15", lldb.SBAddress(
|
||||
ctx_addr + 4*8, target), uint64_t_type).GetValueAsUnsigned()
|
||||
r14 = target.CreateValueFromAddress("r14", lldb.SBAddress(
|
||||
ctx_addr + 3*8, target), uint64_t_type).GetValueAsUnsigned()
|
||||
r13 = target.CreateValueFromAddress("r13", lldb.SBAddress(
|
||||
ctx_addr + 2*8, target), uint64_t_type).GetValueAsUnsigned()
|
||||
r12 = target.CreateValueFromAddress("r12", lldb.SBAddress(
|
||||
ctx_addr + 1*8, target), uint64_t_type).GetValueAsUnsigned()
|
||||
rsp = ctx_addr + 8*8
|
||||
|
||||
print("rip: 0x{:x}\nrsp: 0x{:x}\nrbp: 0x{:x}\nrbx: 0x{:x}\nr15: 0x{:x}\nr14: 0x{:x}\nr13: 0x{:x}\nr12: 0x{:x}".format(
|
||||
rip, rsp, rbp, rbx, r15, r14, r13, r12))
|
||||
|
||||
|
||||
def bthread_reg_restore(debugger, command, result, internal_dict):
|
||||
r"""restore registers"""
|
||||
if not global_state.started:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
for reg_name, reg_value in global_state.saved_regs.items():
|
||||
debugger.HandleCommand(f"register write {reg_name} {reg_value}")
|
||||
|
||||
|
||||
def bthread_end(debugger, command, result, internal_dict):
|
||||
r"""exit bthread debug mode"""
|
||||
if not global_state.started:
|
||||
print("Not in bthread debug mode")
|
||||
return
|
||||
bthread_reg_restore(debugger, command, result, internal_dict)
|
||||
global_state.reset()
|
||||
print("Exit bthread debug mode")
|
||||
|
||||
|
||||
# And the initialization code to add commands.
|
||||
def __lldb_init_module(debugger, internal_dict):
|
||||
debugger.HandleCommand(
|
||||
'command script add -f lldb_bthread_stack.bthread_begin bthread_begin')
|
||||
debugger.HandleCommand(
|
||||
'command script add -f lldb_bthread_stack.bthread_list bthread_list')
|
||||
debugger.HandleCommand(
|
||||
'command script add -f lldb_bthread_stack.bthread_frame bthread_frame')
|
||||
debugger.HandleCommand(
|
||||
'command script add -f lldb_bthread_stack.bthread_num bthread_num')
|
||||
debugger.HandleCommand(
|
||||
'command script add -f lldb_bthread_stack.bthread_all bthread_all')
|
||||
debugger.HandleCommand(
|
||||
'command script add -f lldb_bthread_stack.bthread_meta bthread_meta')
|
||||
debugger.HandleCommand(
|
||||
'command script add -f lldb_bthread_stack.bthread_regs bthread_regs')
|
||||
debugger.HandleCommand(
|
||||
'command script add -f lldb_bthread_stack.bthread_reg_restore bthread_reg_restore')
|
||||
debugger.HandleCommand(
|
||||
'command script add -f lldb_bthread_stack.bthread_end bthread_end')
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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_executable(parallel_http parallel_http.cpp)
|
||||
target_link_libraries(parallel_http PRIVATE brpc-static ${DYNAMIC_LIB})
|
||||
install(TARGETS parallel_http RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
@@ -0,0 +1,211 @@
|
||||
// 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.
|
||||
|
||||
|
||||
// Access many http servers in parallel, much faster than curl (even called in batch)
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <deque>
|
||||
#include <bthread/bthread.h>
|
||||
#include <butil/logging.h>
|
||||
#include <butil/files/scoped_file.h>
|
||||
#include <brpc/channel.h>
|
||||
|
||||
DEFINE_string(url_file, "", "The file containing urls to fetch. If this flag is"
|
||||
" empty, read urls from stdin");
|
||||
DEFINE_int32(timeout_ms, 1000, "RPC timeout in milliseconds");
|
||||
DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)");
|
||||
DEFINE_int32(thread_num, 8, "Number of threads to access urls");
|
||||
DEFINE_int32(concurrency, 1000, "Max number of http calls in parallel");
|
||||
DEFINE_bool(one_line_mode, false, "Output as `URL HTTP-RESPONSE' on true");
|
||||
DEFINE_bool(only_show_host, false, "Print host name only");
|
||||
|
||||
struct AccessThreadArgs {
|
||||
const std::deque<std::string>* url_list;
|
||||
size_t offset;
|
||||
std::deque<std::pair<std::string, butil::IOBuf> > output_queue;
|
||||
butil::Mutex output_queue_mutex;
|
||||
butil::atomic<int> current_concurrency;
|
||||
};
|
||||
|
||||
class OnHttpCallEnd : public google::protobuf::Closure {
|
||||
public:
|
||||
void Run();
|
||||
public:
|
||||
brpc::Controller cntl;
|
||||
AccessThreadArgs* args;
|
||||
std::string url;
|
||||
};
|
||||
|
||||
void OnHttpCallEnd::Run() {
|
||||
std::unique_ptr<OnHttpCallEnd> delete_self(this);
|
||||
{
|
||||
BAIDU_SCOPED_LOCK(args->output_queue_mutex);
|
||||
if (cntl.Failed()) {
|
||||
args->output_queue.push_back(std::make_pair(url, butil::IOBuf()));
|
||||
} else {
|
||||
args->output_queue.push_back(
|
||||
std::make_pair(url, cntl.response_attachment()));
|
||||
}
|
||||
}
|
||||
args->current_concurrency.fetch_sub(1, butil::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void* access_thread(void* void_args) {
|
||||
AccessThreadArgs* args = (AccessThreadArgs*)void_args;
|
||||
brpc::ChannelOptions options;
|
||||
options.protocol = brpc::PROTOCOL_HTTP;
|
||||
options.connect_timeout_ms = FLAGS_timeout_ms / 2;
|
||||
options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/;
|
||||
options.max_retry = FLAGS_max_retry;
|
||||
const int concurrency_for_this_thread = FLAGS_concurrency / FLAGS_thread_num;
|
||||
|
||||
for (size_t i = args->offset; i < args->url_list->size(); i += FLAGS_thread_num) {
|
||||
std::string const& url = (*args->url_list)[i];
|
||||
brpc::Channel channel;
|
||||
if (channel.Init(url.c_str(), &options) != 0) {
|
||||
LOG(ERROR) << "Fail to create channel to url=" << url;
|
||||
BAIDU_SCOPED_LOCK(args->output_queue_mutex);
|
||||
args->output_queue.push_back(std::make_pair(url, butil::IOBuf()));
|
||||
continue;
|
||||
}
|
||||
while (args->current_concurrency.fetch_add(1, butil::memory_order_relaxed)
|
||||
> concurrency_for_this_thread) {
|
||||
args->current_concurrency.fetch_sub(1, butil::memory_order_relaxed);
|
||||
bthread_usleep(5000);
|
||||
}
|
||||
OnHttpCallEnd* done = new OnHttpCallEnd;
|
||||
done->cntl.http_request().uri() = url;
|
||||
done->args = args;
|
||||
done->url = url;
|
||||
channel.CallMethod(NULL, &done->cntl, NULL, NULL, done);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// Parse gflags. We recommend you to use gflags as well.
|
||||
GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
// if (FLAGS_path.empty() || FLAGS_path[0] != '/') {
|
||||
// FLAGS_path = "/" + FLAGS_path;
|
||||
// }
|
||||
|
||||
butil::ScopedFILE fp_guard;
|
||||
FILE* fp = NULL;
|
||||
if (!FLAGS_url_file.empty()) {
|
||||
fp_guard.reset(fopen(FLAGS_url_file.c_str(), "r"));
|
||||
if (!fp_guard) {
|
||||
PLOG(ERROR) << "Fail to open `" << FLAGS_url_file << '\'';
|
||||
return -1;
|
||||
}
|
||||
fp = fp_guard.get();
|
||||
} else {
|
||||
fp = stdin;
|
||||
}
|
||||
char* line_buf = NULL;
|
||||
size_t line_len = 0;
|
||||
ssize_t nr = 0;
|
||||
std::deque<std::string> url_list;
|
||||
while ((nr = getline(&line_buf, &line_len, fp)) != -1) {
|
||||
if (line_buf[nr - 1] == '\n') { // remove ending newline
|
||||
line_buf[nr - 1] = '\0';
|
||||
--nr;
|
||||
}
|
||||
butil::StringPiece line(line_buf, nr);
|
||||
line.trim_spaces();
|
||||
if (!line.empty()) {
|
||||
url_list.push_back(line.as_string());
|
||||
}
|
||||
}
|
||||
if (url_list.empty()) {
|
||||
return 0;
|
||||
}
|
||||
AccessThreadArgs* args = new AccessThreadArgs[FLAGS_thread_num];
|
||||
for (int i = 0; i < FLAGS_thread_num; ++i) {
|
||||
args[i].url_list = &url_list;
|
||||
args[i].offset = i;
|
||||
args[i].current_concurrency.store(0, butil::memory_order_relaxed);
|
||||
}
|
||||
std::vector<bthread_t> tids;
|
||||
tids.resize(FLAGS_thread_num);
|
||||
for (int i = 0; i < FLAGS_thread_num; ++i) {
|
||||
CHECK_EQ(0, bthread_start_background(&tids[i], NULL, access_thread, &args[i]));
|
||||
}
|
||||
std::deque<std::pair<std::string, butil::IOBuf> > output_queue;
|
||||
size_t nprinted = 0;
|
||||
while (nprinted != url_list.size()) {
|
||||
for (int i = 0; i < FLAGS_thread_num; ++i) {
|
||||
{
|
||||
BAIDU_SCOPED_LOCK(args[i].output_queue_mutex);
|
||||
output_queue.swap(args[i].output_queue);
|
||||
}
|
||||
for (size_t i = 0; i < output_queue.size(); ++i) {
|
||||
butil::StringPiece url = output_queue[i].first;
|
||||
butil::StringPiece hostname;
|
||||
if (url.starts_with("http://")) {
|
||||
url.remove_prefix(7);
|
||||
}
|
||||
size_t slash_pos = url.find('/');
|
||||
if (slash_pos != butil::StringPiece::npos) {
|
||||
hostname = url.substr(0, slash_pos);
|
||||
} else {
|
||||
hostname = url;
|
||||
}
|
||||
if (FLAGS_one_line_mode) {
|
||||
if (FLAGS_only_show_host) {
|
||||
std::cout << hostname;
|
||||
} else {
|
||||
std::cout << "http://" << url;
|
||||
}
|
||||
if (output_queue[i].second.empty()) {
|
||||
std::cout << " ERROR" << std::endl;
|
||||
} else {
|
||||
std::cout << ' ' << output_queue[i].second << std::endl;
|
||||
}
|
||||
} else {
|
||||
// The prefix is unlikely be part of a ordinary http body,
|
||||
// thus the line can be easily removed by shell utilities.
|
||||
std::cout << "#### ";
|
||||
if (FLAGS_only_show_host) {
|
||||
std::cout << hostname;
|
||||
} else {
|
||||
std::cout << "http://" << url;
|
||||
}
|
||||
if (output_queue[i].second.empty()) {
|
||||
std::cout << " ERROR" << std::endl;
|
||||
} else {
|
||||
std::cout << '\n' << output_queue[i].second << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
nprinted += output_queue.size();
|
||||
output_queue.clear();
|
||||
}
|
||||
usleep(10000);
|
||||
}
|
||||
|
||||
for (int i = 0; i < FLAGS_thread_num; ++i) {
|
||||
bthread_join(tids[i], NULL);
|
||||
}
|
||||
for (int i = 0; i < FLAGS_thread_num; ++i) {
|
||||
while (args[i].current_concurrency.load(butil::memory_order_relaxed) != 0) {
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 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.
|
||||
|
||||
while read -r line; do
|
||||
val=$(echo "$line" | awk '{print $3}')
|
||||
if [[ $line =~ __clang__ ]]; then
|
||||
CLANG=${val}
|
||||
elif [[ $line =~ __GNUC__ ]]; then
|
||||
GNUC=${val}
|
||||
elif [[ $line =~ __GNUC_MINOR__ ]]; then
|
||||
GNUC_MINOR=${val}
|
||||
elif [[ $line =~ __GNUC_PATCHLEVEL__ ]]; then
|
||||
GNUC_PATCHLEVEL=${val}
|
||||
fi
|
||||
done < <("${CXX:-c++}" -dM -E - < /dev/null | grep "__clang__\|__GNUC__\|__GNUC_MINOR__\|__GNUC_PATCHLEVEL__")
|
||||
|
||||
if [ -n "$GNUC" ] && [ -n "$GNUC_MINOR" ] && [ -n "$GNUC_PATCHLEVEL" ]; then
|
||||
# Calculate GCC/Clang version
|
||||
GCC_VERSION=$((GNUC * 10000 + GNUC_MINOR * 100 + GNUC_PATCHLEVEL))
|
||||
if [ -n "$CLANG" ] && [ "40000" -lt $GCC_VERSION ] && [ $GCC_VERSION -lt "40800" ]; then
|
||||
# Make version of clang >= 4.8 so that it's not rejected by config_brpc.sh
|
||||
GCC_VERSION=40800
|
||||
fi
|
||||
echo $GCC_VERSION
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
@@ -0,0 +1,21 @@
|
||||
# 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.
|
||||
|
||||
file(GLOB SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/tools/rpc_press/*.cpp")
|
||||
add_executable(rpc_press ${SOURCES})
|
||||
target_link_libraries(rpc_press PRIVATE brpc-static ${DYNAMIC_LIB})
|
||||
install(TARGETS rpc_press RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
@@ -0,0 +1,28 @@
|
||||
# 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.
|
||||
|
||||
|
||||
import json
|
||||
import urllib2
|
||||
data = { "message" : "hello world" }
|
||||
request_json = json.dumps(data)
|
||||
req = urllib2.Request("http://127.0.0.1:8000/EchoService/Echo")
|
||||
try:
|
||||
response = urllib2.urlopen(req, request_json, 1)
|
||||
print response.read()
|
||||
except urllib2.HTTPError as e:
|
||||
print e.exception.code
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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 "info_thread.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
InfoThread::InfoThread()
|
||||
: _stop(false)
|
||||
, _tid(0) {
|
||||
pthread_mutex_init(&_mutex, NULL);
|
||||
pthread_cond_init(&_cond, NULL);
|
||||
}
|
||||
|
||||
InfoThread::~InfoThread() {
|
||||
pthread_mutex_destroy(&_mutex);
|
||||
pthread_cond_destroy(&_cond);
|
||||
}
|
||||
|
||||
void InfoThread::run() {
|
||||
int64_t i = 0;
|
||||
int64_t last_sent_count = 0;
|
||||
int64_t last_succ_count = 0;
|
||||
int64_t last_error_count = 0;
|
||||
int64_t start_time = butil::cpuwide_time_us();
|
||||
while (!_stop) {
|
||||
int64_t end_time = 0;
|
||||
while (!_stop &&
|
||||
(end_time = butil::cpuwide_time_us()) < start_time + 1000000L) {
|
||||
BAIDU_SCOPED_LOCK(_mutex);
|
||||
if (!_stop) {
|
||||
timespec ts = butil::microseconds_to_timespec(end_time);
|
||||
pthread_cond_timedwait(&_cond, &_mutex, &ts);
|
||||
}
|
||||
}
|
||||
start_time = butil::cpuwide_time_us();
|
||||
char buf[64];
|
||||
const time_t tm_s = start_time / 1000000L;
|
||||
struct tm lt;
|
||||
strftime(buf, sizeof(buf), "%Y/%m/%d-%H:%M:%S",
|
||||
localtime_r(&tm_s, <));
|
||||
|
||||
const int64_t cur_sent_count = _options.sent_count->get_value();
|
||||
const int64_t cur_succ_count = _options.latency_recorder->count();
|
||||
const int64_t cur_error_count = _options.error_count->get_value();
|
||||
printf("%s\tsent:%-10dsuccess:%-10derror:%-6dtotal_error:%-10lldtotal_sent:%-10lld\n",
|
||||
buf,
|
||||
(int)(cur_sent_count - last_sent_count),
|
||||
(int)(cur_succ_count - last_succ_count),
|
||||
(int)(cur_error_count - last_error_count),
|
||||
(long long)cur_error_count,
|
||||
(long long)cur_sent_count);
|
||||
last_sent_count = cur_sent_count;
|
||||
last_succ_count = cur_succ_count;
|
||||
last_error_count = cur_error_count;
|
||||
|
||||
if (_stop || ++i % 10 == 0) {
|
||||
printf("[Latency]\n"
|
||||
" avg %10lld us\n"
|
||||
" 50%% %10lld us\n"
|
||||
" 70%% %10lld us\n"
|
||||
" 90%% %10lld us\n"
|
||||
" 95%% %10lld us\n"
|
||||
" 97%% %10lld us\n"
|
||||
" 99%% %10lld us\n"
|
||||
" 99.9%% %10lld us\n"
|
||||
" 99.99%% %10lld us\n"
|
||||
" max %10lld us\n",
|
||||
(long long)_options.latency_recorder->latency(),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.5),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.7),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.9),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.95),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.97),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.99),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.999),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.9999),
|
||||
(long long)_options.latency_recorder->max_latency());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void* run_info_thread(void* arg) {
|
||||
((InfoThread*)arg)->run();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool InfoThread::start(const InfoThreadOptions& options) {
|
||||
if (options.latency_recorder == NULL ||
|
||||
options.error_count == NULL ||
|
||||
options.sent_count == NULL) {
|
||||
LOG(ERROR) << "Some required options are NULL";
|
||||
return false;
|
||||
}
|
||||
_options = options;
|
||||
_stop = false;
|
||||
if (pthread_create(&_tid, NULL, run_info_thread, this) != 0) {
|
||||
LOG(ERROR) << "Fail to create info_thread";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InfoThread::stop() {
|
||||
{
|
||||
BAIDU_SCOPED_LOCK(_mutex);
|
||||
if (_stop) {
|
||||
return;
|
||||
}
|
||||
_stop = true;
|
||||
pthread_cond_signal(&_cond);
|
||||
}
|
||||
pthread_join(_tid, NULL);
|
||||
}
|
||||
|
||||
} // namespace brpc
|
||||
@@ -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.
|
||||
|
||||
#ifndef BRPC_RPC_REPLAY_INFO_THREAD_H
|
||||
#define BRPC_RPC_REPLAY_INFO_THREAD_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include <bvar/bvar.h>
|
||||
|
||||
namespace brpc {
|
||||
|
||||
struct InfoThreadOptions {
|
||||
bvar::LatencyRecorder* latency_recorder;
|
||||
bvar::Adder<int64_t>* sent_count;
|
||||
bvar::Adder<int64_t>* error_count;
|
||||
|
||||
InfoThreadOptions()
|
||||
: latency_recorder(NULL)
|
||||
, sent_count(NULL)
|
||||
, error_count(NULL) {}
|
||||
};
|
||||
|
||||
class InfoThread {
|
||||
public:
|
||||
InfoThread();
|
||||
~InfoThread();
|
||||
void run();
|
||||
bool start(const InfoThreadOptions&);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
bool _stop;
|
||||
InfoThreadOptions _options;
|
||||
pthread_mutex_t _mutex;
|
||||
pthread_cond_t _cond;
|
||||
pthread_t _tid;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif //BRPC_RPC_REPLAY_INFO_THREAD_H
|
||||
@@ -0,0 +1,229 @@
|
||||
// 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 <algorithm>
|
||||
#include <butil/logging.h>
|
||||
#include <json2pb/pb_to_json.h>
|
||||
#include <json2pb/json_to_pb.h>
|
||||
#include "pb_util.h"
|
||||
#include "json_loader.h"
|
||||
#include <errno.h>
|
||||
|
||||
namespace brpc {
|
||||
|
||||
class JsonLoader::Reader {
|
||||
public:
|
||||
explicit Reader(int fd2)
|
||||
: _fd(fd2)
|
||||
, _brace_depth(0)
|
||||
, _quoted(false)
|
||||
, _quote_char(0)
|
||||
{}
|
||||
|
||||
explicit Reader(const std::string& jsons)
|
||||
: _fd(-1)
|
||||
, _brace_depth(0)
|
||||
, _quoted(false)
|
||||
, _quote_char(0) {
|
||||
_file_buf.append(jsons);
|
||||
}
|
||||
|
||||
bool get_next_json(butil::IOBuf* json1);
|
||||
bool read_some();
|
||||
|
||||
private:
|
||||
int _fd;
|
||||
int _brace_depth;
|
||||
bool _quoted; // quoted by " or '
|
||||
char _quote_char;
|
||||
butil::IOPortal _file_buf;
|
||||
};
|
||||
|
||||
// Load data from the file.
|
||||
bool JsonLoader::Reader::read_some() {
|
||||
if (_fd < 0) { // loading from a string.
|
||||
return false;
|
||||
}
|
||||
ssize_t nr = _file_buf.append_from_file_descriptor(_fd, 65536);
|
||||
if (nr < 0) {
|
||||
if (errno == EINTR) {
|
||||
return read_some();
|
||||
}
|
||||
PLOG(ERROR) << "Fail to read fd=" << _fd;
|
||||
return false;
|
||||
} else if (nr == 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore json only with spaces and newline
|
||||
static bool possibly_valid_json(const butil::IOBuf& json) {
|
||||
butil::IOBufAsZeroCopyInputStream it(json);
|
||||
const void* data = NULL;
|
||||
for (int size = 0; it.Next(&data, &size); ) {
|
||||
for (int i = 0; i < size; ++i) {
|
||||
char c = ((const char*)data)[i];
|
||||
if (!isspace(c) && c != '\n') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Separate jsons with closed braces.
|
||||
bool JsonLoader::Reader::get_next_json(butil::IOBuf* json1) {
|
||||
if (_file_buf.empty()) {
|
||||
if (!read_some()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
json1->clear();
|
||||
while (1) {
|
||||
butil::IOBufAsZeroCopyInputStream it(_file_buf);
|
||||
const void* data = NULL;
|
||||
int size = 0;
|
||||
int total_size = 0;
|
||||
int skipped = 0;
|
||||
for (; it.Next(&data, &size); total_size += size) {
|
||||
for (int i = 0; i < size; ++i) {
|
||||
const char c = ((const char*)data)[i];
|
||||
if (_brace_depth == 0) {
|
||||
// skip any character until the first left brace is found.
|
||||
if (c != '{') {
|
||||
++skipped;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
switch (c) {
|
||||
case '{':
|
||||
if (!_quoted) {
|
||||
++_brace_depth;
|
||||
} else {
|
||||
VLOG(1) << "Quoted left brace";
|
||||
}
|
||||
break;
|
||||
case '}':
|
||||
if (!_quoted) {
|
||||
--_brace_depth;
|
||||
if (_brace_depth == 0) {
|
||||
// the braces are closed, a complete object.
|
||||
_file_buf.cutn(json1, total_size + i + 1);
|
||||
json1->pop_front(skipped);
|
||||
return possibly_valid_json(*json1);
|
||||
} else if (_brace_depth < 0) {
|
||||
LOG(ERROR) << "More right braces than left braces";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
VLOG(1) << "Quoted right brace";
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
if (_quoted) {
|
||||
if (_quote_char == '"') {
|
||||
_quoted = false;
|
||||
}
|
||||
} else {
|
||||
_quoted = true;
|
||||
_quote_char = '"';
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
if (_quoted) {
|
||||
if (_quote_char == '\'') {
|
||||
_quoted = false;
|
||||
}
|
||||
} else {
|
||||
_quoted = true;
|
||||
_quote_char = '\'';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
// just skip
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_file_buf.empty()) {
|
||||
json1->append(_file_buf);
|
||||
_file_buf.clear();
|
||||
}
|
||||
if (!read_some()) {
|
||||
json1->pop_front(skipped);
|
||||
if (!json1->empty()) {
|
||||
return possibly_valid_json(*json1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonLoader::JsonLoader(google::protobuf::compiler::Importer* importer,
|
||||
google::protobuf::DynamicMessageFactory* factory,
|
||||
const std::string& service_name,
|
||||
const std::string& method_name)
|
||||
: _importer(importer)
|
||||
, _factory(factory)
|
||||
, _service_name(service_name)
|
||||
, _method_name(method_name)
|
||||
{
|
||||
_request_prototype = pbrpcframework::get_prototype_by_name(
|
||||
_service_name, _method_name, true, _importer, _factory);
|
||||
}
|
||||
|
||||
void JsonLoader::load_messages(
|
||||
JsonLoader::Reader* ctx,
|
||||
std::deque<google::protobuf::Message*>* out_msgs) {
|
||||
out_msgs->clear();
|
||||
butil::IOBuf request_json;
|
||||
while (ctx->get_next_json(&request_json)) {
|
||||
VLOG(1) << "Load " << out_msgs->size() + 1 << "-th json=`"
|
||||
<< request_json << '\'';
|
||||
std::string error;
|
||||
google::protobuf::Message* request = _request_prototype->New();
|
||||
butil::IOBufAsZeroCopyInputStream wrapper(request_json);
|
||||
if (!json2pb::JsonToProtoMessage(&wrapper, request, &error)) {
|
||||
LOG(WARNING) << "Fail to convert to pb: " << error << ", json=`"
|
||||
<< request_json << '\'';
|
||||
delete request;
|
||||
continue;
|
||||
}
|
||||
out_msgs->push_back(request);
|
||||
LOG_IF(INFO, (out_msgs->size() % 10000) == 0)
|
||||
<< "Loaded " << out_msgs->size() << " jsons";
|
||||
}
|
||||
}
|
||||
|
||||
void JsonLoader::load_messages(
|
||||
int fd,
|
||||
std::deque<google::protobuf::Message*>* out_msgs) {
|
||||
JsonLoader::Reader ctx(fd);
|
||||
load_messages(&ctx, out_msgs);
|
||||
}
|
||||
|
||||
void JsonLoader::load_messages(
|
||||
const std::string& jsons,
|
||||
std::deque<google::protobuf::Message*>* out_msgs) {
|
||||
JsonLoader::Reader ctx(jsons);
|
||||
load_messages(&ctx, out_msgs);
|
||||
}
|
||||
|
||||
} // 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_JSON_LOADER_H
|
||||
#define BRPC_JSON_LOADER_H
|
||||
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <google/protobuf/message.h>
|
||||
#include <google/protobuf/compiler/importer.h>
|
||||
#include <google/protobuf/dynamic_message.h>
|
||||
#include <butil/iobuf.h>
|
||||
|
||||
namespace brpc {
|
||||
|
||||
// This utility loads pb messages in json format from a file or string.
|
||||
class JsonLoader {
|
||||
public:
|
||||
JsonLoader(google::protobuf::compiler::Importer* importer,
|
||||
google::protobuf::DynamicMessageFactory* factory,
|
||||
const std::string& service_name,
|
||||
const std::string& method_name);
|
||||
~JsonLoader() {}
|
||||
|
||||
// TODO(gejun): messages should be lazily loaded.
|
||||
|
||||
// Load jsons from fd or string, convert them into pb messages, then insert
|
||||
// them into `out_msgs'.
|
||||
void load_messages(int fd, std::deque<google::protobuf::Message*>* out_msgs);
|
||||
void load_messages(const std::string& jsons,
|
||||
std::deque<google::protobuf::Message*>* out_msgs);
|
||||
|
||||
private:
|
||||
class Reader;
|
||||
|
||||
void load_messages(
|
||||
JsonLoader::Reader* ctx,
|
||||
std::deque<google::protobuf::Message*>* out_msgs);
|
||||
|
||||
google::protobuf::compiler::Importer* _importer;
|
||||
google::protobuf::DynamicMessageFactory* _factory;
|
||||
std::string _service_name;
|
||||
std::string _method_name;
|
||||
const google::protobuf::Message* _request_prototype;
|
||||
};
|
||||
|
||||
} // namespace brpc
|
||||
|
||||
#endif // BRPC_JSON_LOADER_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 "pb_util.h"
|
||||
|
||||
using google::protobuf::ServiceDescriptor;
|
||||
using google::protobuf::Descriptor;
|
||||
using google::protobuf::DescriptorPool;
|
||||
using google::protobuf::MessageFactory;
|
||||
using google::protobuf::Message;
|
||||
using google::protobuf::MethodDescriptor;
|
||||
using google::protobuf::compiler::Importer;
|
||||
using google::protobuf::DynamicMessageFactory;
|
||||
using google::protobuf::DynamicMessageFactory;
|
||||
using std::string;
|
||||
|
||||
namespace pbrpcframework {
|
||||
|
||||
const MethodDescriptor* find_method_by_name(const string& service_name,
|
||||
const string& method_name,
|
||||
Importer* importer) {
|
||||
const ServiceDescriptor* descriptor =
|
||||
importer->pool()->FindServiceByName(service_name);
|
||||
if (NULL == descriptor) {
|
||||
LOG(FATAL) << "Fail to find service=" << service_name;
|
||||
return NULL;
|
||||
}
|
||||
return descriptor->FindMethodByName(method_name);
|
||||
}
|
||||
|
||||
const Message* get_prototype_by_method_descriptor(
|
||||
const MethodDescriptor* descripter,
|
||||
bool is_input,
|
||||
DynamicMessageFactory* factory) {
|
||||
if (NULL == descripter) {
|
||||
LOG(FATAL) <<"Param[descripter] is NULL";
|
||||
return NULL;
|
||||
}
|
||||
const Descriptor* message_descriptor = NULL;
|
||||
if (is_input) {
|
||||
message_descriptor = descripter->input_type();
|
||||
} else {
|
||||
message_descriptor = descripter->output_type();
|
||||
}
|
||||
return factory->GetPrototype(message_descriptor);
|
||||
}
|
||||
|
||||
const Message* get_prototype_by_name(const string& service_name,
|
||||
const string& method_name, bool is_input,
|
||||
Importer* importer,
|
||||
DynamicMessageFactory* factory){
|
||||
const MethodDescriptor* descripter = find_method_by_name(
|
||||
service_name, method_name, importer);
|
||||
return get_prototype_by_method_descriptor(descripter, is_input, factory);
|
||||
}
|
||||
|
||||
} // pbrpcframework
|
||||
@@ -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 UTIL_PB_UTIL_H
|
||||
#define UTIL_PB_UTIL_H
|
||||
#include "google/protobuf/message.h"
|
||||
#include "google/protobuf/descriptor.h"
|
||||
#include <google/protobuf/descriptor.pb.h>
|
||||
#include <google/protobuf/dynamic_message.h>
|
||||
#include <google/protobuf/compiler/importer.h>
|
||||
|
||||
namespace pbrpcframework {
|
||||
const google::protobuf::MethodDescriptor* find_method_by_name(
|
||||
const std::string& service_name,
|
||||
const std::string& method_name,
|
||||
google::protobuf::compiler::Importer* importer);
|
||||
|
||||
const google::protobuf::Message* get_prototype_by_method_descriptor(
|
||||
const google::protobuf::MethodDescriptor* descripter,
|
||||
bool is_input,
|
||||
google::protobuf::DynamicMessageFactory* factory);
|
||||
|
||||
const google::protobuf::Message* get_prototype_by_name(
|
||||
const std::string& service_name,
|
||||
const std::string& method_name,
|
||||
bool is_input,
|
||||
google::protobuf::compiler::Importer* importer,
|
||||
google::protobuf::DynamicMessageFactory* factory);
|
||||
}
|
||||
#endif //UTIL_PB_UTIL_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 <gflags/gflags.h>
|
||||
#include <google/protobuf/dynamic_message.h>
|
||||
#include <google/protobuf/compiler/importer.h>
|
||||
#include <brpc/server.h>
|
||||
#include <butil/logging.h>
|
||||
#include <butil/string_splitter.h>
|
||||
#include <string.h>
|
||||
#include "rpc_press_impl.h"
|
||||
|
||||
DEFINE_int32(dummy_port, 8888, "Port of dummy server");
|
||||
DEFINE_string(proto, "", " user's proto files with path");
|
||||
DEFINE_string(inc, "", "Include paths for proto, separated by semicolon(;)");
|
||||
DEFINE_string(method, "example.EchoService.Echo", "The full method name");
|
||||
DEFINE_string(server, "0.0.0.0:8002", "ip:port of the server when -load_balancer is empty, the naming service otherwise");
|
||||
DEFINE_string(input, "", "The file containing requests in json format");
|
||||
DEFINE_string(output, "", "The file containing responses in json format");
|
||||
DEFINE_string(lb_policy, "", "The load balancer algorithm: rr, random, la, p2c, c_murmurhash, c_md5");
|
||||
DEFINE_int32(thread_num, 0, "Number of threads to send requests. 0: automatically chosen according to -qps");
|
||||
DEFINE_string(protocol, "baidu_std", "baidu_std hulu_pbrpc sofa_pbrpc http public_pbrpc nova_pbrpc ubrpc_compack...");
|
||||
DEFINE_string(connection_type, "", "Type of connections: single, pooled, short");
|
||||
DEFINE_int32(timeout_ms, 1000, "RPC timeout in milliseconds");
|
||||
DEFINE_int32(connection_timeout_ms, 500, " connection timeout in milliseconds");
|
||||
DEFINE_int32(max_retry, 3, "Maximum retry times by RPC framework");
|
||||
DEFINE_int32(request_compress_type, 0, "Snappy:1 Gzip:2 Zlib:3 LZ4:4 None:0");
|
||||
DEFINE_int32(response_compress_type, 0, "Snappy:1 Gzip:2 Zlib:3 LZ4:4 None:0");
|
||||
DEFINE_int32(attachment_size, 0, "Carry so many byte attachment along with requests");
|
||||
DEFINE_int32(duration, 0, "how many seconds the press keep");
|
||||
DEFINE_int32(qps, 100 , "how many calls per seconds");
|
||||
DEFINE_bool(pretty, true, "output pretty jsons");
|
||||
|
||||
bool set_press_options(pbrpcframework::PressOptions* options){
|
||||
size_t dot_pos = FLAGS_method.find_last_of('.');
|
||||
if (dot_pos == std::string::npos) {
|
||||
LOG(ERROR) << "-method must be in form of: package.service.method";
|
||||
return false;
|
||||
}
|
||||
options->service = FLAGS_method.substr(0, dot_pos);
|
||||
options->method = FLAGS_method.substr(dot_pos + 1);
|
||||
options->lb_policy = FLAGS_lb_policy;
|
||||
options->test_req_rate = FLAGS_qps;
|
||||
if (FLAGS_thread_num > 0) {
|
||||
options->test_thread_num = FLAGS_thread_num;
|
||||
} else {
|
||||
if (FLAGS_qps <= 0) { // unlimited qps
|
||||
options->test_thread_num = 50;
|
||||
} else {
|
||||
options->test_thread_num = FLAGS_qps / 10000;
|
||||
if (options->test_thread_num < 1) {
|
||||
options->test_thread_num = 1;
|
||||
}
|
||||
if (options->test_thread_num > 50) {
|
||||
options->test_thread_num = 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int rate_limit_per_thread = 1000000;
|
||||
double req_rate_per_thread = options->test_req_rate / options->test_thread_num;
|
||||
if (req_rate_per_thread > rate_limit_per_thread) {
|
||||
LOG(ERROR) << "req_rate: " << (int64_t) req_rate_per_thread << " is too large in one thread. The rate limit is "
|
||||
<< rate_limit_per_thread << " in one thread";
|
||||
return false;
|
||||
}
|
||||
|
||||
options->input = FLAGS_input;
|
||||
options->output = FLAGS_output;
|
||||
options->connection_type = FLAGS_connection_type;
|
||||
options->connect_timeout_ms = FLAGS_connection_timeout_ms;
|
||||
options->timeout_ms = FLAGS_timeout_ms;
|
||||
options->max_retry = FLAGS_max_retry;
|
||||
options->protocol = FLAGS_protocol;
|
||||
options->request_compress_type = FLAGS_request_compress_type;
|
||||
options->response_compress_type = FLAGS_response_compress_type;
|
||||
options->attachment_size = FLAGS_attachment_size;
|
||||
options->host = FLAGS_server;
|
||||
options->proto_file = FLAGS_proto;
|
||||
options->proto_includes = FLAGS_inc;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// Parse gflags. We recommend you to use gflags as well
|
||||
GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
|
||||
// set global log option
|
||||
|
||||
if (FLAGS_dummy_port >= 0) {
|
||||
brpc::StartDummyServerAt(FLAGS_dummy_port);
|
||||
}
|
||||
|
||||
pbrpcframework::PressOptions options;
|
||||
if (!set_press_options(&options)) {
|
||||
return -1;
|
||||
}
|
||||
pbrpcframework::RpcPress* rpc_press = new pbrpcframework::RpcPress;
|
||||
if (0 != rpc_press->init(&options)) {
|
||||
LOG(FATAL) << "Fail to init rpc_press";
|
||||
return -1;
|
||||
}
|
||||
|
||||
rpc_press->start();
|
||||
if (FLAGS_duration <= 0) {
|
||||
while (!brpc::IsAskedToQuit()) {
|
||||
sleep(1);
|
||||
}
|
||||
} else {
|
||||
sleep(FLAGS_duration);
|
||||
}
|
||||
rpc_press->stop();
|
||||
// NOTE(gejun): Can't delete rpc_press on exit. It's probably
|
||||
// used by concurrently running done.
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
// 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 <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <bthread/bthread.h>
|
||||
#include <butil/file_util.h> // butil::FilePath
|
||||
#include <butil/time.h>
|
||||
#include <brpc/channel.h>
|
||||
#include <brpc/controller.h>
|
||||
#include <butil/logging.h>
|
||||
#include <json2pb/pb_to_json.h>
|
||||
#include "json_loader.h"
|
||||
#include "rpc_press_impl.h"
|
||||
|
||||
using google::protobuf::Message;
|
||||
using google::protobuf::Closure;
|
||||
|
||||
namespace pbrpcframework {
|
||||
|
||||
class ImportErrorPrinter
|
||||
: public google::protobuf::compiler::MultiFileErrorCollector {
|
||||
public:
|
||||
// Line and column numbers are zero-based. A line number of -1 indicates
|
||||
// an error with the entire file (e.g. "not found").
|
||||
virtual void AddError(const std::string& filename, int line,
|
||||
int /*column*/, const std::string& message) {
|
||||
LOG_AT(ERROR, filename.c_str(), line) << message;
|
||||
}
|
||||
|
||||
#if GOOGLE_PROTOBUF_VERSION >= 5026000
|
||||
void RecordError(absl::string_view filename, int line, int column,
|
||||
absl::string_view message) override {
|
||||
LOG_AT(ERROR, filename.data(), line) << message;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
int PressClient::init() {
|
||||
brpc::ChannelOptions rpc_options;
|
||||
rpc_options.connect_timeout_ms = _options->connect_timeout_ms;
|
||||
rpc_options.timeout_ms = _options->timeout_ms;
|
||||
rpc_options.max_retry = _options->max_retry;
|
||||
rpc_options.protocol = _options->protocol;
|
||||
rpc_options.connection_type = _options->connection_type;
|
||||
if (_options->attachment_size > 0) {
|
||||
_attachment.clear();
|
||||
_attachment.assign(_options->attachment_size, 'a');
|
||||
}
|
||||
if (_rpc_client.Init(_options->host.c_str(), _options->lb_policy.c_str(),
|
||||
&rpc_options) != 0){
|
||||
LOG(ERROR) << "Fail to initialize channel";
|
||||
return -1;
|
||||
}
|
||||
_method_descriptor = find_method_by_name(
|
||||
_options->service, _options->method, _importer);
|
||||
if (NULL == _method_descriptor) {
|
||||
LOG(ERROR) << "Fail to find method=" << _options->service << '.'
|
||||
<< _options->method;
|
||||
return -1;
|
||||
}
|
||||
_response_prototype = get_prototype_by_method_descriptor(
|
||||
_method_descriptor, false, _factory);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PressClient::call_method(brpc::Controller* cntl, Message* request,
|
||||
Message* response, Closure* done) {
|
||||
if (!_attachment.empty()) {
|
||||
cntl->request_attachment().append(_attachment);
|
||||
}
|
||||
_rpc_client.CallMethod(_method_descriptor, cntl, request, response, done);
|
||||
}
|
||||
|
||||
RpcPress::RpcPress()
|
||||
: _pbrpc_client(NULL)
|
||||
, _started(false)
|
||||
, _stop(false)
|
||||
, _output_json(NULL) {
|
||||
}
|
||||
|
||||
RpcPress::~RpcPress() {
|
||||
if (_output_json) {
|
||||
fclose(_output_json);
|
||||
_output_json = NULL;
|
||||
}
|
||||
delete _importer;
|
||||
}
|
||||
|
||||
int RpcPress::init(const PressOptions* options) {
|
||||
if (NULL == options) {
|
||||
LOG(ERROR) << "Param[options] is NULL" ;
|
||||
return -1;
|
||||
}
|
||||
_options = *options;
|
||||
|
||||
// Import protos.
|
||||
if (_options.proto_file.empty()) {
|
||||
LOG(ERROR) << "-proto is required";
|
||||
return -1;
|
||||
}
|
||||
int pos = _options.proto_file.find_last_of('/');
|
||||
std::string proto_file(_options.proto_file.substr(pos + 1));
|
||||
std::string proto_path(_options.proto_file.substr(0, pos));
|
||||
google::protobuf::compiler::DiskSourceTree sourceTree;
|
||||
// look up .proto file in the same directory
|
||||
sourceTree.MapPath("", proto_path.c_str());
|
||||
// Add paths in -inc
|
||||
if (!_options.proto_includes.empty()) {
|
||||
butil::StringSplitter sp(_options.proto_includes.c_str(), ';');
|
||||
for (; sp; ++sp) {
|
||||
sourceTree.MapPath("", std::string(sp.field(), sp.length()));
|
||||
}
|
||||
}
|
||||
ImportErrorPrinter error_printer;
|
||||
_importer = new google::protobuf::compiler::Importer(&sourceTree, &error_printer);
|
||||
if (_importer->Import(proto_file.c_str()) == NULL) {
|
||||
LOG(ERROR) << "Fail to import " << proto_file;
|
||||
return -1;
|
||||
}
|
||||
|
||||
_pbrpc_client = new PressClient(&_options, _importer, &_factory);
|
||||
|
||||
if (!_options.output.empty()) {
|
||||
butil::File::Error error;
|
||||
butil::FilePath path(_options.output);
|
||||
butil::FilePath dir = path.DirName();
|
||||
if (!butil::CreateDirectoryAndGetError(dir, &error)) {
|
||||
LOG(ERROR) << "Fail to create directory=`" << dir.value()
|
||||
<< "', " << error;
|
||||
return -1;
|
||||
}
|
||||
_output_json = fopen(_options.output.c_str(), "w");
|
||||
LOG_IF(ERROR, !_output_json) << "Fail to open " << _options.output;
|
||||
}
|
||||
|
||||
int ret = _pbrpc_client->init();
|
||||
if (0 != ret) {
|
||||
LOG(ERROR) << "Fail to initialize rpc client";
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (_options.input.empty()) {
|
||||
LOG(ERROR) << "-input is empty";
|
||||
return -1;
|
||||
}
|
||||
brpc::JsonLoader json_util(_importer, &_factory,
|
||||
_options.service, _options.method);
|
||||
if (butil::PathExists(butil::FilePath(_options.input))) {
|
||||
int fd = open(_options.input.c_str(), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
PLOG(ERROR) << "Fail to open " << _options.input;
|
||||
return -1;
|
||||
}
|
||||
json_util.load_messages(fd, &_msgs);
|
||||
} else {
|
||||
json_util.load_messages(_options.input, &_msgs);
|
||||
}
|
||||
if (_msgs.empty()) {
|
||||
LOG(ERROR) << "Fail to load requests";
|
||||
return -1;
|
||||
}
|
||||
LOG(INFO) << "Loaded " << _msgs.size() << " requests";
|
||||
_latency_recorder.expose("rpc_press");
|
||||
_error_count.expose("rpc_press_error_count");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* RpcPress::sync_call_thread(void* arg) {
|
||||
((RpcPress*)arg)->sync_client();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void RpcPress::handle_response(brpc::Controller* cntl,
|
||||
Message* request,
|
||||
Message* response,
|
||||
int64_t start_time){
|
||||
if (!cntl->Failed()){
|
||||
int64_t rpc_call_time_us = butil::cpuwide_time_us() - start_time;
|
||||
_latency_recorder << rpc_call_time_us;
|
||||
|
||||
if (_output_json) {
|
||||
std::string response_json;
|
||||
std::string error;
|
||||
if (!json2pb::ProtoMessageToJson(*response, &response_json, &error)) {
|
||||
LOG(WARNING) << "Fail to convert to json: " << error;
|
||||
}
|
||||
fprintf(_output_json, "%s\n", response_json.c_str());
|
||||
}
|
||||
} else {
|
||||
LOG(WARNING) << "error_code=" << cntl->ErrorCode() << ", "
|
||||
<< cntl->ErrorText();
|
||||
_error_count << 1;
|
||||
}
|
||||
delete response;
|
||||
delete cntl;
|
||||
}
|
||||
|
||||
static butil::atomic<int> g_thread_count(0);
|
||||
|
||||
void RpcPress::sync_client() {
|
||||
double req_rate = _options.test_req_rate / _options.test_thread_num;
|
||||
//max make up time is 5 s
|
||||
if (_msgs.empty()) {
|
||||
LOG(ERROR) << "nothing to send!";
|
||||
return;
|
||||
}
|
||||
const int thread_index = g_thread_count.fetch_add(1, butil::memory_order_relaxed);
|
||||
int msg_index = thread_index;
|
||||
int64_t last_expected_time = butil::monotonic_time_ns();
|
||||
const int64_t interval = (int64_t) (1000000000L / req_rate);
|
||||
// the max tolerant delay between end_time and expected_time. 10ms or 10 intervals
|
||||
int64_t max_tolerant_delay = std::max((int64_t) 10000000L, 10 * interval);
|
||||
while (!_stop) {
|
||||
brpc::Controller* cntl = new brpc::Controller;
|
||||
msg_index = (msg_index + _options.test_thread_num) % _msgs.size();
|
||||
Message* request = _msgs[msg_index];
|
||||
Message* response = _pbrpc_client->get_output_message();
|
||||
const int64_t start_time = butil::cpuwide_time_us();
|
||||
google::protobuf::Closure* done = brpc::NewCallback<
|
||||
RpcPress,
|
||||
RpcPress*,
|
||||
brpc::Controller*,
|
||||
Message*,
|
||||
Message*, int64_t>
|
||||
(this, &RpcPress::handle_response, cntl, request, response, start_time);
|
||||
const brpc::CallId cid1 = cntl->call_id();
|
||||
_pbrpc_client->call_method(cntl, request, response, done);
|
||||
_sent_count << 1;
|
||||
|
||||
if (_options.test_req_rate <= 0) {
|
||||
brpc::Join(cid1);
|
||||
} else {
|
||||
int64_t end_time = butil::monotonic_time_ns();
|
||||
int64_t expected_time = last_expected_time + interval;
|
||||
if (end_time < expected_time) {
|
||||
usleep((expected_time - end_time)/1000);
|
||||
}
|
||||
if (end_time - expected_time > max_tolerant_delay) {
|
||||
expected_time = end_time;
|
||||
}
|
||||
last_expected_time = expected_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int RpcPress::start() {
|
||||
_ttid.resize(_options.test_thread_num);
|
||||
int ret = 0;
|
||||
for (int i = 0; i < _options.test_thread_num; i++) {
|
||||
if ((ret = pthread_create(&_ttid[i], NULL, sync_call_thread, this)) != 0) {
|
||||
LOG(ERROR) << "Fail to create sending threads";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
brpc::InfoThreadOptions info_thr_opt;
|
||||
info_thr_opt.latency_recorder = &_latency_recorder;
|
||||
info_thr_opt.error_count = &_error_count;
|
||||
info_thr_opt.sent_count = &_sent_count;
|
||||
if (!_info_thr.start(info_thr_opt)) {
|
||||
LOG(ERROR) << "Fail to create stats thread";
|
||||
return -1;
|
||||
}
|
||||
_started = true;
|
||||
return 0;
|
||||
}
|
||||
int RpcPress::stop() {
|
||||
if (!_started) {
|
||||
return -1;
|
||||
}
|
||||
_stop = true;
|
||||
for (size_t i = 0; i < _ttid.size(); i++) {
|
||||
pthread_join(_ttid[i], NULL);
|
||||
}
|
||||
_info_thr.stop();
|
||||
return 0;
|
||||
}
|
||||
} //namespace
|
||||
@@ -0,0 +1,141 @@
|
||||
// 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 PBRPCPRESS_PBRPC_PRESS_H
|
||||
#define PBRPCPRESS_PBRPC_PRESS_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <deque>
|
||||
#include <google/protobuf/compiler/importer.h>
|
||||
#include <google/protobuf/dynamic_message.h>
|
||||
#include <bvar/bvar.h>
|
||||
#include <brpc/channel.h>
|
||||
#include "info_thread.h"
|
||||
#include "pb_util.h"
|
||||
|
||||
namespace pbrpcframework {
|
||||
class JsonUtil;
|
||||
|
||||
struct PressOptions {
|
||||
std::string service; //service name (packet.rpcservice)
|
||||
std::string method; //method name (rpc service method)
|
||||
int server_type; // server type: 0 = hulu server, 1 = old pbrpc server, 2 = sofa server
|
||||
double test_req_rate; // 0 = no limit
|
||||
int test_thread_num;
|
||||
std::string input;
|
||||
std::string output;
|
||||
std::string host; // server's ip:port, used by hulu server and sofa server
|
||||
std::string channel; // server's channel, used by old pbrpc server
|
||||
//comcfg::Configure conf;
|
||||
std::string conf_dir;
|
||||
std::string conf_file;
|
||||
std::string connection_type; // connection type 0:SINGLE 1:POOLED 2:SHORT
|
||||
int connect_timeout_ms; // connection timeout in milliseconds
|
||||
int timeout_ms; // RPC timeout in milliseconds
|
||||
int max_retry; // Maximum retry times by RPC framework
|
||||
std::string protocol;
|
||||
int request_compress_type; // Snappy:1 Gzip:2 Zlib:3 LZ4:4 None:0
|
||||
int response_compress_type; // Snappy:1 Gzip:2 Zlib:3 LZ4:4 None:0
|
||||
int attachment_size; // Snappy:1 Gzip:2 Zlib:3 LZ4:4 None:0
|
||||
bool auth;// Enable Giano authentication
|
||||
std::string auth_group;
|
||||
std::string lb_policy; // "rr", "Policy of load balance rr ||random"
|
||||
std::string proto_file;
|
||||
std::string proto_includes;
|
||||
|
||||
PressOptions() :
|
||||
server_type(0),
|
||||
test_req_rate(0),
|
||||
test_thread_num(1),
|
||||
connect_timeout_ms(1000),
|
||||
timeout_ms(1000),
|
||||
max_retry(3),
|
||||
request_compress_type(0),
|
||||
response_compress_type(0),
|
||||
attachment_size(0),
|
||||
auth(false)
|
||||
{}
|
||||
};
|
||||
|
||||
class PressClient {
|
||||
public:
|
||||
PressClient(const PressOptions* options,
|
||||
google::protobuf::compiler::Importer* importer,
|
||||
google::protobuf::DynamicMessageFactory* factory) {
|
||||
_method_descriptor = NULL;
|
||||
_response_prototype = NULL;
|
||||
_options = options;
|
||||
_importer = importer;
|
||||
_factory = factory;
|
||||
|
||||
}
|
||||
|
||||
google::protobuf::Message* get_output_message() {
|
||||
return _response_prototype->New();
|
||||
}
|
||||
|
||||
int init();
|
||||
void call_method(brpc::Controller* cntl,
|
||||
google::protobuf::Message* request,
|
||||
google::protobuf::Message* response,
|
||||
google::protobuf::Closure* done);
|
||||
public:
|
||||
brpc::Channel _rpc_client;
|
||||
std::string _attachment;
|
||||
const PressOptions* _options;
|
||||
const google::protobuf::MethodDescriptor* _method_descriptor;
|
||||
const google::protobuf::Message* _response_prototype;
|
||||
google::protobuf::compiler::Importer* _importer;
|
||||
google::protobuf::DynamicMessageFactory* _factory;
|
||||
};
|
||||
|
||||
class RpcPress {
|
||||
public:
|
||||
RpcPress();
|
||||
~RpcPress();
|
||||
int init(const PressOptions* options);
|
||||
int start();
|
||||
int stop();
|
||||
const PressOptions* options() { return &_options; }
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(RpcPress);
|
||||
|
||||
bool new_pbrpc_press_client_by_client_type(int client_type);
|
||||
void sync_client();
|
||||
void handle_response(brpc::Controller* cntl,
|
||||
google::protobuf::Message* request,
|
||||
google::protobuf::Message* response,
|
||||
int64_t start_time_ns);
|
||||
static void* sync_call_thread(void* arg);
|
||||
|
||||
bvar::LatencyRecorder _latency_recorder;
|
||||
bvar::Adder<int64_t> _error_count;
|
||||
bvar::Adder<int64_t> _sent_count;
|
||||
std::deque<google::protobuf::Message*> _msgs;
|
||||
PressClient* _pbrpc_client;
|
||||
PressOptions _options;
|
||||
bool _started;
|
||||
bool _stop;
|
||||
FILE* _output_json;
|
||||
google::protobuf::compiler::Importer* _importer;
|
||||
google::protobuf::DynamicMessageFactory _factory;
|
||||
std::vector<pthread_t> _ttid;
|
||||
brpc::InfoThread _info_thr;
|
||||
};
|
||||
}
|
||||
#endif // PBRPCPRESS_PBRPC_PRESS_H
|
||||
@@ -0,0 +1,21 @@
|
||||
# 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.
|
||||
|
||||
file(GLOB SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/tools/rpc_replay/*.cpp")
|
||||
add_executable(rpc_replay ${SOURCES})
|
||||
target_link_libraries(rpc_replay PRIVATE brpc-static ${DYNAMIC_LIB})
|
||||
install(TARGETS rpc_replay RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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 "info_thread.h"
|
||||
|
||||
namespace brpc {
|
||||
|
||||
InfoThread::InfoThread()
|
||||
: _stop(false)
|
||||
, _tid(0) {
|
||||
pthread_mutex_init(&_mutex, NULL);
|
||||
pthread_cond_init(&_cond, NULL);
|
||||
}
|
||||
|
||||
InfoThread::~InfoThread() {
|
||||
pthread_mutex_destroy(&_mutex);
|
||||
pthread_cond_destroy(&_cond);
|
||||
}
|
||||
|
||||
void InfoThread::run() {
|
||||
int64_t i = 0;
|
||||
int64_t last_sent_count = 0;
|
||||
int64_t last_succ_count = 0;
|
||||
int64_t last_error_count = 0;
|
||||
int64_t start_time = butil::cpuwide_time_us();
|
||||
while (!_stop) {
|
||||
int64_t end_time = 0;
|
||||
while (!_stop &&
|
||||
(end_time = butil::cpuwide_time_us()) < start_time + 1000000L) {
|
||||
BAIDU_SCOPED_LOCK(_mutex);
|
||||
if (!_stop) {
|
||||
timespec ts = butil::microseconds_to_timespec(end_time);
|
||||
pthread_cond_timedwait(&_cond, &_mutex, &ts);
|
||||
}
|
||||
}
|
||||
start_time = butil::cpuwide_time_us();
|
||||
char buf[64];
|
||||
const time_t tm_s = start_time / 1000000L;
|
||||
struct tm lt;
|
||||
strftime(buf, sizeof(buf), "%Y/%m/%d-%H:%M:%S",
|
||||
localtime_r(&tm_s, <));
|
||||
|
||||
const int64_t cur_sent_count = _options.sent_count->get_value();
|
||||
const int64_t cur_succ_count = _options.latency_recorder->count();
|
||||
const int64_t cur_error_count = _options.error_count->get_value();
|
||||
printf("%s\tsent:%-10dsuccess:%-10derror:%-10dtotal_error:%-10lldtotal_sent:%-10lld\n",
|
||||
buf,
|
||||
(int)(cur_sent_count - last_sent_count),
|
||||
(int)(cur_succ_count - last_succ_count),
|
||||
(int)(cur_error_count - last_error_count),
|
||||
(long long)cur_error_count,
|
||||
(long long)cur_sent_count);
|
||||
last_sent_count = cur_sent_count;
|
||||
last_succ_count = cur_succ_count;
|
||||
last_error_count = cur_error_count;
|
||||
|
||||
if (_stop || ++i % 10 == 0) {
|
||||
printf("[Latency]\n"
|
||||
" avg %10lld us\n"
|
||||
" 50%% %10lld us\n"
|
||||
" 70%% %10lld us\n"
|
||||
" 90%% %10lld us\n"
|
||||
" 95%% %10lld us\n"
|
||||
" 97%% %10lld us\n"
|
||||
" 99%% %10lld us\n"
|
||||
" 99.9%% %10lld us\n"
|
||||
" 99.99%% %10lld us\n"
|
||||
" max %10lld us\n",
|
||||
(long long)_options.latency_recorder->latency(),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.5),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.7),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.9),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.95),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.97),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.99),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.999),
|
||||
(long long)_options.latency_recorder->latency_percentile(0.9999),
|
||||
(long long)_options.latency_recorder->max_latency());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void* run_info_thread(void* arg) {
|
||||
((InfoThread*)arg)->run();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool InfoThread::start(const InfoThreadOptions& options) {
|
||||
if (options.latency_recorder == NULL ||
|
||||
options.error_count == NULL ||
|
||||
options.sent_count == NULL) {
|
||||
LOG(ERROR) << "Some required options are NULL";
|
||||
return false;
|
||||
}
|
||||
_options = options;
|
||||
_stop = false;
|
||||
if (pthread_create(&_tid, NULL, run_info_thread, this) != 0) {
|
||||
LOG(ERROR) << "Fail to create info_thread";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InfoThread::stop() {
|
||||
{
|
||||
BAIDU_SCOPED_LOCK(_mutex);
|
||||
if (_stop) {
|
||||
return;
|
||||
}
|
||||
_stop = true;
|
||||
pthread_cond_signal(&_cond);
|
||||
}
|
||||
pthread_join(_tid, NULL);
|
||||
}
|
||||
|
||||
} // brpc
|
||||
@@ -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.
|
||||
|
||||
#ifndef BRPC_RPC_REPLAY_INFO_THREAD_H
|
||||
#define BRPC_RPC_REPLAY_INFO_THREAD_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include <bvar/bvar.h>
|
||||
|
||||
namespace brpc {
|
||||
|
||||
struct InfoThreadOptions {
|
||||
bvar::LatencyRecorder* latency_recorder;
|
||||
bvar::Adder<int64_t>* sent_count;
|
||||
bvar::Adder<int64_t>* error_count;
|
||||
|
||||
InfoThreadOptions()
|
||||
: latency_recorder(NULL)
|
||||
, sent_count(NULL)
|
||||
, error_count(NULL) {}
|
||||
};
|
||||
|
||||
class InfoThread {
|
||||
public:
|
||||
InfoThread();
|
||||
~InfoThread();
|
||||
void run();
|
||||
bool start(const InfoThreadOptions&);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
bool _stop;
|
||||
InfoThreadOptions _options;
|
||||
pthread_mutex_t _mutex;
|
||||
pthread_cond_t _cond;
|
||||
pthread_t _tid;
|
||||
};
|
||||
|
||||
} // brpc
|
||||
|
||||
#endif //BRPC_RPC_REPLAY_INFO_THREAD_H
|
||||
@@ -0,0 +1,301 @@
|
||||
// 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>
|
||||
#include <butil/logging.h>
|
||||
#include <butil/time.h>
|
||||
#include <butil/macros.h>
|
||||
#include <butil/file_util.h>
|
||||
#include <bvar/bvar.h>
|
||||
#include <bthread/bthread.h>
|
||||
#include <brpc/channel.h>
|
||||
#include <brpc/server.h>
|
||||
#include <brpc/rpc_dump.h>
|
||||
#include <brpc/serialized_request.h>
|
||||
#include <brpc/nshead_message.h>
|
||||
#include <brpc/details/http_message.h>
|
||||
#include "brpc/options.pb.h"
|
||||
#include "info_thread.h"
|
||||
|
||||
DEFINE_string(dir, "", "The directory of dumped requests");
|
||||
DEFINE_int32(times, 1, "Repeat replaying for so many times");
|
||||
DEFINE_int32(qps, 0, "Limit QPS if this flag is positive");
|
||||
DEFINE_int32(thread_num, 0, "Number of threads for replaying");
|
||||
DEFINE_bool(use_bthread, true, "Use bthread to replay");
|
||||
DEFINE_string(connection_type, "", "Connection type, choose automatically "
|
||||
"according to protocol by default");
|
||||
DEFINE_string(server, "0.0.0.0:8002", "IP Address of server");
|
||||
DEFINE_string(load_balancer, "", "The algorithm for load balancing");
|
||||
DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds");
|
||||
DEFINE_int32(max_retry, 3, "Maximum retry times");
|
||||
DEFINE_int32(dummy_port, 8899, "Port of dummy server(to monitor replaying)");
|
||||
DEFINE_string(http_host, "", "Host field for http protocol");
|
||||
|
||||
bvar::LatencyRecorder g_latency_recorder("rpc_replay");
|
||||
bvar::Adder<int64_t> g_error_count("rpc_replay_error_count");
|
||||
bvar::Adder<int64_t> g_sent_count;
|
||||
|
||||
// Include channels for all protocols that support both client and server.
|
||||
class ChannelGroup {
|
||||
public:
|
||||
int Init();
|
||||
|
||||
~ChannelGroup();
|
||||
|
||||
// Get channel by protocol type.
|
||||
brpc::Channel* channel(brpc::ProtocolType type) {
|
||||
if ((size_t)type < _chans.size()) {
|
||||
return _chans[(size_t)type];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<brpc::Channel*> _chans;
|
||||
};
|
||||
|
||||
int ChannelGroup::Init() {
|
||||
{
|
||||
// force global initialization of rpc.
|
||||
brpc::Channel dummy_channel;
|
||||
}
|
||||
std::vector<std::pair<brpc::ProtocolType, brpc::Protocol> > protocols;
|
||||
brpc::ListProtocols(&protocols);
|
||||
size_t max_protocol_size = 0;
|
||||
for (size_t i = 0; i < protocols.size(); ++i) {
|
||||
max_protocol_size = std::max(max_protocol_size,
|
||||
(size_t)protocols[i].first);
|
||||
}
|
||||
_chans.resize(max_protocol_size + 1);
|
||||
for (size_t i = 0; i < protocols.size(); ++i) {
|
||||
const brpc::ProtocolType protocol_type = protocols[i].first;
|
||||
const brpc::Protocol protocol = protocols[i].second;
|
||||
brpc::ChannelOptions options;
|
||||
options.protocol = protocol_type;
|
||||
options.connection_type = FLAGS_connection_type;
|
||||
options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/;
|
||||
options.max_retry = FLAGS_max_retry;
|
||||
if ((options.connection_type == brpc::CONNECTION_TYPE_UNKNOWN ||
|
||||
options.connection_type & protocol.supported_connection_type) &&
|
||||
protocol.support_client() &&
|
||||
protocol.support_server()) {
|
||||
brpc::Channel* chan = new brpc::Channel;
|
||||
if (chan->Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(),
|
||||
&options) != 0) {
|
||||
LOG(ERROR) << "Fail to initialize channel";
|
||||
delete chan;
|
||||
return -1;
|
||||
}
|
||||
_chans[protocol_type] = chan;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ChannelGroup::~ChannelGroup() {
|
||||
for (size_t i = 0; i < _chans.size(); ++i) {
|
||||
delete _chans[i];
|
||||
}
|
||||
_chans.clear();
|
||||
}
|
||||
|
||||
static void handle_response(brpc::Controller* cntl, int64_t start_time,
|
||||
bool sleep_on_error/*note*/) {
|
||||
// TODO(gejun): some bthreads are starved when new bthreads are created
|
||||
// continuously, which happens when server is down and RPC keeps failing.
|
||||
// Sleep a while on error to avoid that now.
|
||||
const int64_t end_time = butil::cpuwide_time_us();
|
||||
const int64_t elp = end_time - start_time;
|
||||
if (!cntl->Failed()) {
|
||||
g_latency_recorder << elp;
|
||||
} else {
|
||||
g_error_count << 1;
|
||||
if (sleep_on_error) {
|
||||
bthread_usleep(10000);
|
||||
}
|
||||
}
|
||||
delete cntl;
|
||||
}
|
||||
|
||||
butil::atomic<int> g_thread_offset(0);
|
||||
|
||||
static void* replay_thread(void* arg) {
|
||||
ChannelGroup* chan_group = static_cast<ChannelGroup*>(arg);
|
||||
const int thread_offset = g_thread_offset.fetch_add(1, butil::memory_order_relaxed);
|
||||
double req_rate = FLAGS_qps / (double)FLAGS_thread_num;
|
||||
brpc::SerializedRequest req;
|
||||
brpc::NsheadMessage nshead_req;
|
||||
int64_t last_expected_time = butil::monotonic_time_ns();
|
||||
const int64_t interval = (int64_t) (1000000000L / req_rate);
|
||||
// the max tolerant delay between end_time and expected_time. 10ms or 10 intervals
|
||||
int64_t max_tolerant_delay = std::max((int64_t) 10000000L, 10 * interval);
|
||||
for (int i = 0; !brpc::IsAskedToQuit() && i < FLAGS_times; ++i) {
|
||||
brpc::SampleIterator it(FLAGS_dir);
|
||||
int j = 0;
|
||||
for (brpc::SampledRequest* sample = it.Next();
|
||||
!brpc::IsAskedToQuit() && sample != NULL; sample = it.Next(), ++j) {
|
||||
std::unique_ptr<brpc::SampledRequest> sample_guard(sample);
|
||||
if ((j % FLAGS_thread_num) != thread_offset) {
|
||||
continue;
|
||||
}
|
||||
brpc::Channel* chan =
|
||||
chan_group->channel(sample->meta.protocol_type());
|
||||
if (chan == NULL) {
|
||||
LOG(ERROR) << "No channel on protocol="
|
||||
<< sample->meta.protocol_type();
|
||||
continue;
|
||||
}
|
||||
|
||||
brpc::Controller* cntl = new brpc::Controller;
|
||||
req.Clear();
|
||||
|
||||
google::protobuf::Message* req_ptr = &req;
|
||||
cntl->reset_sampled_request(sample_guard.release());
|
||||
if (sample->meta.protocol_type() == brpc::PROTOCOL_HTTP) {
|
||||
brpc::HttpMessage http_message;
|
||||
http_message.ParseFromIOBuf(sample->request);
|
||||
cntl->http_request().Swap(http_message.header());
|
||||
if (!FLAGS_http_host.empty()) {
|
||||
// reset Host in header
|
||||
cntl->http_request().SetHeader("Host", FLAGS_http_host);
|
||||
}
|
||||
cntl->request_attachment() = http_message.body().movable();
|
||||
req_ptr = NULL;
|
||||
} else if (sample->meta.protocol_type() == brpc::PROTOCOL_NSHEAD) {
|
||||
nshead_req.Clear();
|
||||
memcpy(&nshead_req.head, sample->meta.nshead().c_str(), sample->meta.nshead().length());
|
||||
nshead_req.body = sample->request;
|
||||
req_ptr = &nshead_req;
|
||||
} else if (sample->meta.attachment_size() > 0) {
|
||||
sample->request.cutn(
|
||||
&req.serialized_data(),
|
||||
sample->request.size() - sample->meta.attachment_size());
|
||||
cntl->request_attachment() = sample->request.movable();
|
||||
} else {
|
||||
req.serialized_data() = sample->request.movable();
|
||||
}
|
||||
g_sent_count << 1;
|
||||
const int64_t start_time = butil::cpuwide_time_us();
|
||||
if (FLAGS_qps <= 0) {
|
||||
chan->CallMethod(NULL/*use rpc_dump_context in cntl instead*/,
|
||||
cntl, req_ptr, NULL/*ignore response*/, NULL);
|
||||
handle_response(cntl, start_time, true);
|
||||
} else {
|
||||
google::protobuf::Closure* done =
|
||||
brpc::NewCallback(handle_response, cntl, start_time, false);
|
||||
chan->CallMethod(NULL/*use rpc_dump_context in cntl instead*/,
|
||||
cntl, req_ptr, NULL/*ignore response*/, done);
|
||||
int64_t end_time = butil::monotonic_time_ns();
|
||||
int64_t expected_time = last_expected_time + interval;
|
||||
if (end_time < expected_time) {
|
||||
usleep((expected_time - end_time)/1000);
|
||||
}
|
||||
if (end_time - expected_time > max_tolerant_delay) {
|
||||
expected_time = end_time;
|
||||
}
|
||||
last_expected_time = expected_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// Parse gflags. We recommend you to use gflags as well.
|
||||
GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_dir.empty() ||
|
||||
!butil::DirectoryExists(butil::FilePath(FLAGS_dir))) {
|
||||
LOG(ERROR) << "--dir=<dir-of-dumped-files> is required";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (FLAGS_dummy_port >= 0) {
|
||||
brpc::StartDummyServerAt(FLAGS_dummy_port);
|
||||
}
|
||||
|
||||
ChannelGroup chan_group;
|
||||
if (chan_group.Init() != 0) {
|
||||
LOG(ERROR) << "Fail to init ChannelGroup";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (FLAGS_thread_num <= 0) {
|
||||
if (FLAGS_qps <= 0) { // unlimited qps
|
||||
FLAGS_thread_num = 50;
|
||||
} else {
|
||||
FLAGS_thread_num = FLAGS_qps / 10000;
|
||||
if (FLAGS_thread_num < 1) {
|
||||
FLAGS_thread_num = 1;
|
||||
}
|
||||
if (FLAGS_thread_num > 50) {
|
||||
FLAGS_thread_num = 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int rate_limit_per_thread = 1000000;
|
||||
int req_rate_per_thread = FLAGS_qps / FLAGS_thread_num;
|
||||
if (req_rate_per_thread > rate_limit_per_thread) {
|
||||
LOG(ERROR) << "req_rate: " << (int64_t) req_rate_per_thread << " is too large in one thread. The rate limit is "
|
||||
<< rate_limit_per_thread << " in one thread";
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<bthread_t> bids;
|
||||
std::vector<pthread_t> pids;
|
||||
if (!FLAGS_use_bthread) {
|
||||
pids.resize(FLAGS_thread_num);
|
||||
for (int i = 0; i < FLAGS_thread_num; ++i) {
|
||||
if (pthread_create(&pids[i], NULL, replay_thread, &chan_group) != 0) {
|
||||
LOG(ERROR) << "Fail to create pthread";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bids.resize(FLAGS_thread_num);
|
||||
for (int i = 0; i < FLAGS_thread_num; ++i) {
|
||||
if (bthread_start_background(
|
||||
&bids[i], NULL, replay_thread, &chan_group) != 0) {
|
||||
LOG(ERROR) << "Fail to create bthread";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
brpc::InfoThread info_thr;
|
||||
brpc::InfoThreadOptions info_thr_opt;
|
||||
info_thr_opt.latency_recorder = &g_latency_recorder;
|
||||
info_thr_opt.error_count = &g_error_count;
|
||||
info_thr_opt.sent_count = &g_sent_count;
|
||||
|
||||
if (!info_thr.start(info_thr_opt)) {
|
||||
LOG(ERROR) << "Fail to create info_thread";
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < FLAGS_thread_num; ++i) {
|
||||
if (!FLAGS_use_bthread) {
|
||||
pthread_join(pids[i], NULL);
|
||||
} else {
|
||||
bthread_join(bids[i], NULL);
|
||||
}
|
||||
}
|
||||
info_thr.stop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# 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(FindProtobuf)
|
||||
protobuf_generate_cpp(PROTO_SRC PROTO_HEADER view.proto)
|
||||
|
||||
add_executable(rpc_view rpc_view.cpp ${PROTO_SRC})
|
||||
target_include_directories(rpc_view PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_link_libraries(rpc_view PRIVATE brpc-static ${DYNAMIC_LIB})
|
||||
install(TARGETS rpc_view RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
@@ -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>
|
||||
#include <butil/logging.h>
|
||||
#include <brpc/server.h>
|
||||
#include <brpc/channel.h>
|
||||
#include "view.pb.h"
|
||||
|
||||
DEFINE_int32(port, 8888, "TCP Port of this server");
|
||||
DEFINE_string(target, "", "The server to view");
|
||||
DEFINE_int32(timeout_ms, 5000, "Timeout for calling the server to view");
|
||||
|
||||
// handle HTTP response of accessing builtin services of the target server.
|
||||
static void handle_response(brpc::Controller* client_cntl,
|
||||
std::string target,
|
||||
brpc::Controller* server_cntl,
|
||||
google::protobuf::Closure* server_done) {
|
||||
// Copy all headers. The "Content-Length" will be overwriteen.
|
||||
server_cntl->http_response() = client_cntl->http_response();
|
||||
// Copy content.
|
||||
server_cntl->response_attachment() = client_cntl->response_attachment();
|
||||
// Insert "rpc_view: <target>" before </body> so that users are always
|
||||
// visually notified with target server w/o confusions.
|
||||
butil::IOBuf& content = server_cntl->response_attachment();
|
||||
butil::IOBuf before_body;
|
||||
if (content.cut_until(&before_body, "</body>") == 0) {
|
||||
before_body.append(
|
||||
"<style type=\"text/css\">\n"
|
||||
".rpcviewlogo {position: fixed; bottom: 0px; right: 0px;"
|
||||
" color: #ffffff; background-color: #000000; }\n"
|
||||
" </style>\n"
|
||||
"<span class='rpcviewlogo'> rpc_view: ");
|
||||
before_body.append(target);
|
||||
before_body.append(" </span></body>");
|
||||
before_body.append(content);
|
||||
content = before_body;
|
||||
}
|
||||
// Notice that we don't set RPC to failed on http errors because we
|
||||
// want to pass unchanged content to the users otherwise RPC replaces
|
||||
// the content with ErrorText.
|
||||
if (client_cntl->Failed() &&
|
||||
client_cntl->ErrorCode() != brpc::EHTTP) {
|
||||
server_cntl->SetFailed(client_cntl->ErrorCode(),
|
||||
"%s", client_cntl->ErrorText().c_str());
|
||||
}
|
||||
delete client_cntl;
|
||||
server_done->Run();
|
||||
}
|
||||
|
||||
// A http_master_service.
|
||||
class ViewServiceImpl : public ViewService {
|
||||
public:
|
||||
ViewServiceImpl() {}
|
||||
virtual ~ViewServiceImpl() {}
|
||||
virtual void default_method(google::protobuf::RpcController* cntl_base,
|
||||
const HttpRequest*,
|
||||
HttpResponse*,
|
||||
google::protobuf::Closure* done) {
|
||||
brpc::ClosureGuard done_guard(done);
|
||||
brpc::Controller* server_cntl =
|
||||
static_cast<brpc::Controller*>(cntl_base);
|
||||
|
||||
// Get or set target. Notice that we don't access FLAGS_target directly
|
||||
// which is thread-unsafe (for string flags).
|
||||
std::string target;
|
||||
const std::string* newtarget =
|
||||
server_cntl->http_request().uri().GetQuery("changetarget");
|
||||
if (newtarget) {
|
||||
if (GFLAGS_NAMESPACE::SetCommandLineOption("target", newtarget->c_str()).empty()) {
|
||||
server_cntl->SetFailed("Fail to change value of -target");
|
||||
return;
|
||||
}
|
||||
target = *newtarget;
|
||||
} else {
|
||||
if (!GFLAGS_NAMESPACE::GetCommandLineOption("target", &target)) {
|
||||
server_cntl->SetFailed("Fail to get value of -target");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the http channel on-the-fly. Notice that we've set
|
||||
// `defer_close_second' in main() so that dtor of channels do not
|
||||
// close connections immediately and ad-hoc creation of channels
|
||||
// often reuses the not-yet-closed connections.
|
||||
brpc::Channel http_chan;
|
||||
brpc::ChannelOptions http_chan_opt;
|
||||
http_chan_opt.protocol = brpc::PROTOCOL_HTTP;
|
||||
if (http_chan.Init(target.c_str(), &http_chan_opt) != 0) {
|
||||
server_cntl->SetFailed(brpc::EINTERNAL,
|
||||
"Fail to connect to %s", target.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove "Accept-Encoding". We need to insert additional texts
|
||||
// before </body>, preventing the server from compressing the content
|
||||
// simplifies our code. The additional bandwidth consumption shouldn't
|
||||
// be an issue for infrequent checking out of builtin services pages.
|
||||
server_cntl->http_request().RemoveHeader("Accept-Encoding");
|
||||
|
||||
brpc::Controller* client_cntl = new brpc::Controller;
|
||||
client_cntl->http_request() = server_cntl->http_request();
|
||||
// Remove "Host" so that RPC will laterly serialize the (correct)
|
||||
// target server in.
|
||||
client_cntl->http_request().RemoveHeader("host");
|
||||
|
||||
// Setup the URI.
|
||||
const brpc::URI& server_uri = server_cntl->http_request().uri();
|
||||
std::string uri = server_uri.path();
|
||||
if (!server_uri.query().empty()) {
|
||||
uri.push_back('?');
|
||||
uri.append(server_uri.query());
|
||||
}
|
||||
if (!server_uri.fragment().empty()) {
|
||||
uri.push_back('#');
|
||||
uri.append(server_uri.fragment());
|
||||
}
|
||||
client_cntl->http_request().uri() = uri;
|
||||
|
||||
// /hotspots pages may take a long time to finish, since they all have
|
||||
// query "seconds", we set the timeout to be longer than "seconds".
|
||||
const std::string* seconds =
|
||||
server_cntl->http_request().uri().GetQuery("seconds");
|
||||
int64_t timeout_ms = FLAGS_timeout_ms;
|
||||
if (seconds) {
|
||||
timeout_ms += atoll(seconds->c_str()) * 1000;
|
||||
}
|
||||
client_cntl->set_timeout_ms(timeout_ms);
|
||||
|
||||
// Keep content as it is.
|
||||
client_cntl->request_attachment() = server_cntl->request_attachment();
|
||||
|
||||
http_chan.CallMethod(NULL, client_cntl, NULL, NULL,
|
||||
brpc::NewCallback(
|
||||
handle_response, client_cntl, target,
|
||||
server_cntl, done_guard.release()));
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (FLAGS_target.empty() &&
|
||||
(argc != 2 ||
|
||||
GFLAGS_NAMESPACE::SetCommandLineOption("target", argv[1]).empty())) {
|
||||
LOG(ERROR) << "Usage: ./rpc_view <ip>:<port>";
|
||||
return -1;
|
||||
}
|
||||
// This keeps ad-hoc creation of channels reuse previous connections.
|
||||
GFLAGS_NAMESPACE::SetCommandLineOption("defer_close_second", "10");
|
||||
|
||||
brpc::Server server;
|
||||
server.set_version("rpc_view_server");
|
||||
brpc::ServerOptions server_opt;
|
||||
server_opt.http_master_service = new ViewServiceImpl;
|
||||
if (server.Start(FLAGS_port, &server_opt) != 0) {
|
||||
LOG(ERROR) << "Fail to start ViewServer";
|
||||
return -1;
|
||||
}
|
||||
server.RunUntilAskedToQuit();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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";
|
||||
option cc_generic_services = true;
|
||||
|
||||
message HttpRequest {};
|
||||
message HttpResponse {};
|
||||
|
||||
service ViewService {
|
||||
rpc default_method(HttpRequest) returns (HttpResponse);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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_executable(trackme_server trackme_server.cpp)
|
||||
target_link_libraries(trackme_server PRIVATE brpc-static ${DYNAMIC_LIB})
|
||||
install(TARGETS trackme_server RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
@@ -0,0 +1,280 @@
|
||||
// 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.
|
||||
|
||||
|
||||
// A server to receive TrackMeRequest and send back TrackMeResponse.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <memory>
|
||||
#include <butil/logging.h>
|
||||
#include <brpc/server.h>
|
||||
#include <butil/files/file_watcher.h>
|
||||
#include <butil/files/scoped_file.h>
|
||||
#include <brpc/trackme.pb.h>
|
||||
|
||||
DEFINE_string(bug_file, "./bugs", "A file containing revision and information of bugs");
|
||||
DEFINE_int32(port, 8877, "TCP Port of this server");
|
||||
DEFINE_int32(reporting_interval, 300, "Reporting interval of clients");
|
||||
|
||||
struct RevisionInfo {
|
||||
int64_t min_rev;
|
||||
int64_t max_rev;
|
||||
brpc::TrackMeSeverity severity;
|
||||
std::string error_text;
|
||||
};
|
||||
|
||||
// Load bugs from a file periodically.
|
||||
class BugsLoader {
|
||||
public:
|
||||
BugsLoader();
|
||||
bool start(const std::string& bugs_file);
|
||||
void stop();
|
||||
bool find(int64_t revision, brpc::TrackMeResponse* response);
|
||||
private:
|
||||
void load_bugs();
|
||||
void run();
|
||||
static void* run_this(void* arg);
|
||||
typedef std::vector<RevisionInfo> BugList;
|
||||
std::string _bugs_file;
|
||||
bool _started;
|
||||
bool _stop;
|
||||
pthread_t _tid;
|
||||
std::shared_ptr<BugList> _bug_list;
|
||||
};
|
||||
|
||||
class TrackMeServiceImpl : public brpc::TrackMeService {
|
||||
public:
|
||||
explicit TrackMeServiceImpl(BugsLoader* bugs) : _bugs(bugs) {
|
||||
}
|
||||
~TrackMeServiceImpl() {}
|
||||
void TrackMe(google::protobuf::RpcController* cntl_base,
|
||||
const brpc::TrackMeRequest* request,
|
||||
brpc::TrackMeResponse* response,
|
||||
google::protobuf::Closure* done) {
|
||||
brpc::ClosureGuard done_guard(done);
|
||||
brpc::Controller* cntl = (brpc::Controller*)cntl_base;
|
||||
// Set to OK by default.
|
||||
response->set_severity(brpc::TrackMeOK);
|
||||
// Check if the version is affected by bugs if client set it.
|
||||
if (request->has_rpc_version()) {
|
||||
_bugs->find(request->rpc_version(), response);
|
||||
}
|
||||
response->set_new_interval(FLAGS_reporting_interval);
|
||||
butil::EndPoint server_addr;
|
||||
CHECK_EQ(0, butil::str2endpoint(request->server_addr().c_str(), &server_addr));
|
||||
// NOTE(gejun): The ip reported is inaccessible in many cases, use
|
||||
// remote_side instead right now.
|
||||
server_addr.ip = cntl->remote_side().ip;
|
||||
LOG(INFO) << "Pinged by " << server_addr << " (r"
|
||||
<< request->rpc_version() << ")";
|
||||
}
|
||||
|
||||
private:
|
||||
BugsLoader* _bugs;
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
brpc::Server server;
|
||||
server.set_version("trackme_server");
|
||||
BugsLoader bugs;
|
||||
if (!bugs.start(FLAGS_bug_file)) {
|
||||
LOG(ERROR) << "Fail to start BugsLoader";
|
||||
return -1;
|
||||
}
|
||||
TrackMeServiceImpl echo_service_impl(&bugs);
|
||||
if (server.AddService(&echo_service_impl,
|
||||
brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
|
||||
LOG(ERROR) << "Fail to add service";
|
||||
return -1;
|
||||
}
|
||||
brpc::ServerOptions options;
|
||||
// I've noticed that many connections do not report. Don't know the
|
||||
// root cause yet. Set the idle_time to keep connections clean.
|
||||
options.idle_timeout_sec = FLAGS_reporting_interval * 2;
|
||||
if (server.Start(FLAGS_port, &options) != 0) {
|
||||
LOG(ERROR) << "Fail to start TrackMeServer";
|
||||
return -1;
|
||||
}
|
||||
server.RunUntilAskedToQuit();
|
||||
bugs.stop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
BugsLoader::BugsLoader()
|
||||
: _started(false)
|
||||
, _stop(false)
|
||||
, _tid(0)
|
||||
{}
|
||||
|
||||
bool BugsLoader::start(const std::string& bugs_file) {
|
||||
_bugs_file = bugs_file;
|
||||
if (pthread_create(&_tid, NULL, run_this, this) != 0) {
|
||||
LOG(ERROR) << "Fail to create loading thread";
|
||||
return false;
|
||||
}
|
||||
_started = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BugsLoader::stop() {
|
||||
if (!_started) {
|
||||
return;
|
||||
}
|
||||
_stop = true;
|
||||
pthread_join(_tid, NULL);
|
||||
}
|
||||
|
||||
void* BugsLoader::run_this(void* arg) {
|
||||
((BugsLoader*)arg)->run();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void BugsLoader::run() {
|
||||
// Check status of _bugs_files periodically.
|
||||
butil::FileWatcher fw;
|
||||
if (fw.init(_bugs_file.c_str()) < 0) {
|
||||
LOG(ERROR) << "Fail to init FileWatcher on `" << _bugs_file << "'";
|
||||
return;
|
||||
}
|
||||
while (!_stop) {
|
||||
load_bugs();
|
||||
while (!_stop) {
|
||||
butil::FileWatcher::Change change = fw.check_and_consume();
|
||||
if (change > 0) {
|
||||
break;
|
||||
}
|
||||
if (change < 0) {
|
||||
LOG(ERROR) << "`" << _bugs_file << "' was deleted";
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void BugsLoader::load_bugs() {
|
||||
butil::ScopedFILE fp(fopen(_bugs_file.c_str(), "r"));
|
||||
if (!fp) {
|
||||
PLOG(WARNING) << "Fail to open `" << _bugs_file << '\'';
|
||||
return;
|
||||
}
|
||||
|
||||
char* line = NULL;
|
||||
size_t line_len = 0;
|
||||
ssize_t nr = 0;
|
||||
int nline = 0;
|
||||
std::unique_ptr<BugList> m(new BugList);
|
||||
while ((nr = getline(&line, &line_len, fp.get())) != -1) {
|
||||
++nline;
|
||||
if (line[nr - 1] == '\n') { // remove ending newline
|
||||
--nr;
|
||||
}
|
||||
// line format:
|
||||
// min_rev <sp> max_rev <sp> severity <sp> description
|
||||
butil::StringMultiSplitter sp(line, line + nr, " \t");
|
||||
if (!sp) {
|
||||
continue;
|
||||
}
|
||||
long long min_rev;
|
||||
if (sp.to_longlong(&min_rev)) {
|
||||
LOG(WARNING) << "[line" << nline << "] Fail to parse column1 as min_rev";
|
||||
continue;
|
||||
}
|
||||
++sp;
|
||||
long long max_rev;
|
||||
if (!sp || sp.to_longlong(&max_rev)) {
|
||||
LOG(WARNING) << "[line" << nline << "] Fail to parse column2 as max_rev";
|
||||
continue;
|
||||
}
|
||||
if (max_rev < min_rev) {
|
||||
LOG(WARNING) << "[line" << nline << "] max_rev=" << max_rev
|
||||
<< " is less than min_rev=" << min_rev;
|
||||
continue;
|
||||
}
|
||||
++sp;
|
||||
if (!sp) {
|
||||
LOG(WARNING) << "[line" << nline << "] Fail to parse column3 as severity";
|
||||
continue;
|
||||
}
|
||||
brpc::TrackMeSeverity severity = brpc::TrackMeOK;
|
||||
butil::StringPiece severity_str(sp.field(), sp.length());
|
||||
if (severity_str == "f" || severity_str == "F") {
|
||||
severity = brpc::TrackMeFatal;
|
||||
} else if (severity_str == "w" || severity_str == "W") {\
|
||||
severity = brpc::TrackMeWarning;
|
||||
} else {
|
||||
LOG(WARNING) << "[line" << nline << "] Invalid severity=" << severity_str;
|
||||
continue;
|
||||
}
|
||||
++sp;
|
||||
if (!sp) {
|
||||
LOG(WARNING) << "[line" << nline << "] Fail to parse column4 as string";
|
||||
continue;
|
||||
}
|
||||
// Treat everything until end of the line as description. So don't add
|
||||
// comments starting with # or //, they are not recognized.
|
||||
butil::StringPiece description(sp.field(), line + nr - sp.field());
|
||||
RevisionInfo info;
|
||||
info.min_rev = min_rev;
|
||||
info.max_rev = max_rev;
|
||||
info.severity = severity;
|
||||
info.error_text.assign(description.data(), description.size());
|
||||
m->push_back(info);
|
||||
}
|
||||
LOG(INFO) << "Loaded " << m->size() << " bugs";
|
||||
free(line);
|
||||
// Just reseting the shared_ptr. Previous BugList will be destroyed when
|
||||
// no threads reference it.
|
||||
_bug_list.reset(m.release());
|
||||
}
|
||||
|
||||
bool BugsLoader::find(int64_t revision, brpc::TrackMeResponse* response) {
|
||||
// Add reference to make sure the bug list is not deleted.
|
||||
std::shared_ptr<BugList> local_list = _bug_list;
|
||||
if (local_list.get() == NULL) {
|
||||
return false;
|
||||
}
|
||||
// Reading the list in this function is always safe because a BugList
|
||||
// is never changed after creation.
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < local_list->size(); ++i) {
|
||||
const RevisionInfo & info = (*local_list)[i];
|
||||
if (info.min_rev <= revision && revision <= info.max_rev) {
|
||||
found = true;
|
||||
if (info.severity > response->severity()) {
|
||||
response->set_severity(info.severity);
|
||||
}
|
||||
if (info.severity != brpc::TrackMeOK) {
|
||||
std::string* error = response->mutable_error_text();
|
||||
char prefix[64];
|
||||
if (info.min_rev != info.max_rev) {
|
||||
snprintf(prefix, sizeof(prefix), "[r%lld-r%lld] ",
|
||||
(long long)info.min_rev, (long long)info.max_rev);
|
||||
} else {
|
||||
snprintf(prefix, sizeof(prefix), "[r%lld] ",
|
||||
(long long)info.min_rev);
|
||||
}
|
||||
error->append(prefix);
|
||||
error->append(info.error_text);
|
||||
error->append("; ");
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- function
|
||||
-- Proto.new(name, desc)
|
||||
-- proto.dissector
|
||||
-- proto.fields
|
||||
-- proto.prefs
|
||||
-- proto.prefs_changed
|
||||
-- proto.init
|
||||
-- proto.name
|
||||
-- proto.description
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local plugin_info = {
|
||||
name = "baidu",
|
||||
version = "0.1.0",
|
||||
description = "baidu_std"
|
||||
}
|
||||
|
||||
local major, minor, patch = get_version():match("(%d+)%.(%d+)%.(%d+)")
|
||||
local version_code = tonumber(major) * 1000 * 1000 + tonumber(minor) * 1000 + tonumber(patch)
|
||||
|
||||
local proto = Proto.new(plugin_info.name, plugin_info.description)
|
||||
|
||||
local LM_DBG = function(fmt, ...)
|
||||
if (tonumber(major) < 3) then
|
||||
critical(table.concat({ plugin_info.name:upper() .. ":", fmt }, ' '):format(...))
|
||||
else
|
||||
print (table.concat({ plugin_info.name:upper() .. ":", fmt }, ' '):format(...))
|
||||
end
|
||||
end
|
||||
|
||||
LM_DBG("Wireshark version =", get_version())
|
||||
LM_DBG("Lua version =", _VERSION)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local MAGIC_CODE_PRPC = "PRPC"
|
||||
local MAGIC_CODE_STRM = "STRM"
|
||||
|
||||
local PROTO_HEADER_LENGTH = 12
|
||||
|
||||
|
||||
local pf_magic = ProtoField.string(plugin_info.name .. ".magic",
|
||||
"Magic Code", BASE_NONE)
|
||||
local pf_body_size = ProtoField.uint32(plugin_info.name .. ".body_size",
|
||||
"Body Size", BASE_DEC)
|
||||
local pf_meta_size = ProtoField.uint32(plugin_info.name .. ".meta_size",
|
||||
"Meta Size", BASE_DEC)
|
||||
|
||||
-- used to customize tree item
|
||||
local pf_any = ProtoField.bytes (plugin_info.name .. ".any",
|
||||
"Any", BASE_NONE)
|
||||
|
||||
proto.fields = {
|
||||
pf_magic,
|
||||
pf_body_size,
|
||||
pf_meta_size,
|
||||
pf_any
|
||||
}
|
||||
|
||||
----------------------------------------
|
||||
-- ref fields
|
||||
local proto_f_magic_code = Field.new(plugin_info.name .. ".magic")
|
||||
local proto_f_body_size = Field.new(plugin_info.name .. ".body_size")
|
||||
local proto_f_meta_size = Field.new(plugin_info.name .. ".meta_size")
|
||||
|
||||
local proto_f_protobuf_field_name = Field.new("protobuf.field.name")
|
||||
local proto_f_protobuf_field_value = Field.new("protobuf.field.value")
|
||||
|
||||
----------------------------------------
|
||||
-- protobuf dissector
|
||||
-- Note:
|
||||
-- options.proto, baidu_rpc_meta.proto and streaming_rpc_meta.proto
|
||||
-- should be put in Wireshark Protobuf Search Paths.
|
||||
local protobuf_dissector = Dissector.get("protobuf")
|
||||
|
||||
----------------------------------------
|
||||
-- declare functions
|
||||
|
||||
local check_length = function() end
|
||||
local dissect_proto = function() end
|
||||
|
||||
----------------------------------------
|
||||
-- main dissector
|
||||
function proto.dissector(tvbuf, pktinfo, root)
|
||||
local pktlen = tvbuf:len()
|
||||
|
||||
local bytes_consumed = 0
|
||||
|
||||
while bytes_consumed < pktlen do
|
||||
local result = dissect_proto(tvbuf, pktinfo, root, bytes_consumed)
|
||||
|
||||
if result > 0 then
|
||||
bytes_consumed = bytes_consumed + result
|
||||
elseif result == 0 then
|
||||
-- hit an error
|
||||
return 0
|
||||
else
|
||||
pktinfo.desegment_offset = bytes_consumed
|
||||
-- require more bytes
|
||||
pktinfo.desegment_len = -result
|
||||
|
||||
return pktlen
|
||||
end
|
||||
end
|
||||
|
||||
return bytes_consumed
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- heuristic
|
||||
local function heur_dissect_proto(tvbuf, pktinfo, root)
|
||||
LM_DBG("heur brpc: pkg number: " .. pktinfo.number)
|
||||
if (tvbuf:len() < PROTO_HEADER_LENGTH) then
|
||||
LM_DBG("too short: pkg number: " .. pktinfo.number)
|
||||
return false
|
||||
end
|
||||
|
||||
local magic = tvbuf:range(0, 4):string()
|
||||
-- for range dissectors
|
||||
if magic ~= MAGIC_CODE_PRPC and maigc ~= MAGIC_CODE_STRM then
|
||||
LM_DBG("invalid magic code: pkg number: " .. pktinfo.number)
|
||||
return false
|
||||
end
|
||||
|
||||
proto.dissector(tvbuf, pktinfo, root)
|
||||
|
||||
pktinfo.conversation = proto
|
||||
|
||||
return true
|
||||
end
|
||||
proto:register_heuristic("tcp", heur_dissect_proto)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local correlation_method_map = {}
|
||||
|
||||
local store_method = function(correlation_id, method)
|
||||
-- TODO: pop items
|
||||
correlation_method_map[correlation_id] = method
|
||||
end
|
||||
|
||||
local load_method = function(correlation_id)
|
||||
return correlation_method_map[correlation_id]
|
||||
end
|
||||
|
||||
-- check packet length, return length of packet if valid
|
||||
check_length = function(tvbuf, offset)
|
||||
local msglen = tvbuf:len() - offset
|
||||
|
||||
if msglen ~= tvbuf:reported_length_remaining(offset) then
|
||||
-- captured packets are being sliced/cut-off, so don't try to desegment/reassemble
|
||||
LM_WARN("Captured packet was shorter than original, can't reassemble")
|
||||
return 0
|
||||
end
|
||||
|
||||
if msglen < PROTO_HEADER_LENGTH then
|
||||
-- we need more bytes, so tell the main dissector function that we
|
||||
-- didn't dissect anything, and we need an unknown number of more
|
||||
-- bytes (which is what "DESEGMENT_ONE_MORE_SEGMENT" is used for)
|
||||
return -DESEGMENT_ONE_MORE_SEGMENT
|
||||
end
|
||||
|
||||
-- if we got here, then we know we have enough bytes in the Tvb buffer
|
||||
-- to at least figure out whether this is valid baidu_std packet
|
||||
|
||||
local magic = tvbuf:range(offset, 4):string()
|
||||
if magic ~= MAGIC_CODE_PRPC and magic ~= MAGIC_CODE_STRM then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- big endian
|
||||
local packet_size = tvbuf:range(offset+4, 4):uint() + PROTO_HEADER_LENGTH
|
||||
if msglen < packet_size then
|
||||
LM_DBG("Need more bytes to desegment full baidu_std packet")
|
||||
return -(packet_size - msglen)
|
||||
end
|
||||
|
||||
return packet_size
|
||||
end
|
||||
|
||||
-- convert uint32 integer to byte array
|
||||
function uint32_to_byte_array(value)
|
||||
local bytes = {}
|
||||
while value > 0 do
|
||||
table.insert(bytes, bit32.band(value, 0xFF))
|
||||
value = bit32.rshift(value, 8)
|
||||
end
|
||||
return bytes
|
||||
end
|
||||
|
||||
-- varint decoding function
|
||||
function decode_varint(value)
|
||||
local bytes = uint32_to_byte_array(value)
|
||||
local result = 0
|
||||
local shift = 0
|
||||
|
||||
for _, byte in ipairs(bytes) do
|
||||
result = bit32.bor(result, bit32.lshift(bit32.band(byte, 0x7F), shift))
|
||||
if bit32.band(byte, 0x80) == 0 then
|
||||
return result
|
||||
end
|
||||
shift = shift + 7
|
||||
end
|
||||
|
||||
LM_DBG("Malformed Varint encoding")
|
||||
end
|
||||
|
||||
----------------------------------------
|
||||
dissect_proto = function(tvbuf, pktinfo, root, offset)
|
||||
local len = check_length(tvbuf, offset)
|
||||
if len <= 0 then
|
||||
return len
|
||||
end
|
||||
|
||||
local tree = root:add(proto, tvbuf:range(offset, len))
|
||||
|
||||
tree:add(pf_magic, tvbuf:range(offset, 4))
|
||||
tree:add(pf_body_size, tvbuf:range(offset+4, 4))
|
||||
tree:add(pf_meta_size, tvbuf:range(offset+8, 4))
|
||||
|
||||
local magic_code = tvbuf:range(offset, 4):string()
|
||||
local meta_size = tvbuf:range(offset + 8, 4):uint()
|
||||
local body_size = tvbuf:range(offset + 4, 4):uint()
|
||||
local tvb_meta = tvbuf:range(offset + PROTO_HEADER_LENGTH, meta_size):tvb()
|
||||
if magic_code == MAGIC_CODE_PRPC then
|
||||
-- update 'Protocol' field
|
||||
if offset == 0 then
|
||||
pktinfo.cols.protocol:set("ProtoBuf")
|
||||
end
|
||||
-- dissect rpc meta fields
|
||||
pktinfo.private["pb_msg_type"] = "message,brpc.policy.RpcMeta"
|
||||
|
||||
-- solve the problem of parsing errors when multiple RPCs are included in a packet
|
||||
local protobuf_field_names = { proto_f_protobuf_field_name() }
|
||||
local pre_field_nums = #protobuf_field_names
|
||||
protobuf_dissector:call(tvb_meta, pktinfo, tree)
|
||||
-- https://ask.wireshark.org/question/31800/protobuf-dissector-with-nested-structures/?answer=31924#post-id-31924
|
||||
protobuf_field_names = { proto_f_protobuf_field_name() }
|
||||
local cur_field_nums = #protobuf_field_names
|
||||
local protobuf_field_values = { proto_f_protobuf_field_value() }
|
||||
|
||||
local direction, method
|
||||
local service_name, method_name, correlation_id, attachment_size
|
||||
|
||||
-- only process new fields added after the previous call to protobuf_dissector
|
||||
for k = pre_field_nums + 1, cur_field_nums do
|
||||
local v = protobuf_field_names[k]
|
||||
if v.value == "request" then
|
||||
direction = "request"
|
||||
elseif v.value == "response" then
|
||||
direction = "response"
|
||||
elseif v.value == "service_name" then
|
||||
service_name = protobuf_field_values[k].range:string(ENC_UTF8)
|
||||
elseif v.value == "method_name" then
|
||||
method_name = protobuf_field_values[k].range:string(ENC_UTF8)
|
||||
elseif v.value == "correlation_id" then
|
||||
correlation_id = protobuf_field_values[k].range:uint64()
|
||||
elseif v.value == "attachment_size" then
|
||||
attachment_size = protobuf_field_values[k].range:le_uint()
|
||||
end
|
||||
end
|
||||
|
||||
if direction == "request" then
|
||||
method = service_name .. "/" .. method_name
|
||||
-- NOTE: convert uint64 to string to be used as key of table
|
||||
store_method(tostring(correlation_id), method)
|
||||
elseif direction == "response" then
|
||||
method = load_method(tostring(correlation_id))
|
||||
end
|
||||
|
||||
if attachment_size then
|
||||
attachment_size = decode_varint(attachment_size)
|
||||
else
|
||||
attachment_size = 0
|
||||
end
|
||||
|
||||
if method ~= nil then
|
||||
-- dissect rpc body
|
||||
local tvb_body = tvbuf:range(offset + PROTO_HEADER_LENGTH + meta_size, body_size - meta_size - attachment_size):tvb()
|
||||
pktinfo.private["pb_msg_type"] = "application/grpc,/" .. method .. "," .. direction
|
||||
protobuf_dissector:call(tvb_body, pktinfo, tree)
|
||||
end
|
||||
elseif magic_code == MAGIC_CODE_STRM then
|
||||
-- dissect streaming meta fields
|
||||
pktinfo.private["pb_msg_type"] = "message,brpc.StreamFrameMeta"
|
||||
protobuf_dissector:call(tvb_meta, pktinfo, tree)
|
||||
end
|
||||
|
||||
-- body fields are business related, customized here
|
||||
|
||||
return len
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Editor modelines
|
||||
--
|
||||
-- Local variables:
|
||||
-- c-basic-offset: 4
|
||||
-- tab-width: 4
|
||||
-- indent-tab-mode: nil
|
||||
-- End:
|
||||
--
|
||||
-- kate: indent-width 4; tab-width 4;
|
||||
-- vim: tabstop=4:softtabstop=4:shiftwidth=4:expandtab
|
||||
-- :indentSize=4:tabSize=4:noTabs=true
|
||||
Reference in New Issue
Block a user