chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:29:29 +08:00
commit f52cf32e56
1718 changed files with 366293 additions and 0 deletions
+24
View File
@@ -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)
+27
View File
@@ -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
+294
View File
@@ -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()
+27
View File
@@ -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
+347
View File
@@ -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')
+20
View File
@@ -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})
+211
View File
@@ -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;
}
+41
View File
@@ -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
+21
View File
@@ -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})
+28
View File
@@ -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
+130
View File
@@ -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, &lt));
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
+55
View File
@@ -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
+229
View File
@@ -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
+63
View File
@@ -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
+72
View File
@@ -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
+44
View File
@@ -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
+129
View File
@@ -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;
}
+297
View File
@@ -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
+141
View File
@@ -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
+21
View File
@@ -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})
+130
View File
@@ -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, &lt));
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
+55
View File
@@ -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
+301
View File
@@ -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;
}
+24
View File
@@ -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})
+176
View File
@@ -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'>&nbsp;rpc_view: ");
before_body.append(target);
before_body.append("&nbsp;</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;
}
+26
View File
@@ -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);
};
+20
View File
@@ -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})
+280
View File
@@ -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;
}
+325
View File
@@ -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