Android bluetooth start discovery analyse

本文详细介绍了从用户界面点击蓝牙搜索到系统底层实现的完整过程,包括Android应用层、JNI层、协议栈及与硬件交互的具体步骤。
AI助手已提取文章相关产品:
一. start_discovery的上层一系列的调用
1. 界面上的"search for device"
在./device/softwinner/common/packages/TvdSettings/src/com/android/settings/bluetooth/BluetoothSettings.java中
当点击“searching devices"或者打开时就会去扫描蓝牙设备
  1. public boolean onOptionsItemSelected(MenuItem item) {
  2.         switch (item.getItemId()) {
  3.             case MENU_ID_SCAN:
  4.                 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
  5.                     startScanning();
  6.                 }
  7.                 return true;
  8. }
在device/softwinner/common/packages/TvdSettings/src/com/android/settings/bluetooth/BluetoothSettings.java中
  1. private void startScanning() {
  2.         if (!mAvailableDevicesCategoryIsPresent) {
  3.             getPreferenceScreen().addPreference(mAvailableDevicesCategory);
  4.         }
  5.         mLocalAdapter.startScanning(true);
  6.     }
在./device/softwinner/common/packages/TvdSettings/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java中
  1. void startScanning(boolean force) {
  2.         if (!mAdapter.isDiscovering()) {
  3.             if (!force) {
  4.                 if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
  5.                     return;
  6.                 }
  7.                 A2dpProfile a2dp = mProfileManager.getA2dpProfile();
  8.                 if (a2dp != null && a2dp.isA2dpPlaying()) {
  9.                     return;
  10.                 }
  11.             }

  12.             if (mAdapter.startDiscovery()) {
  13.                 mLastScan = System.currentTimeMillis();
  14.             }
  15.         }
  16.     }
2. frameworks中的调用
在frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java中
  1. public boolean startDiscovery() {
  2.         if (getState() != STATE_ON) return false;
  3.         try {
  4.             synchronized(mManagerCallback) {
  5.                 if (mService != null) return mService.startDiscovery();
  6.             }
  7.         } catch (RemoteException e) {Log.e(TAG, "", e);} 
  8.         return false;
  9.     }
mService用binder调用startDiscovery
3. packages中的调用
在./packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java中
  1. private static class AdapterServiceBinder extends IBluetooth.Stub {
  2. public boolean startDiscovery() {
  3.             if (!Utils.checkCaller()) {
  4.                 Log.w(TAG,"startDiscovery(): not allowed for non-active user");
  5.                 return false;
  6.             }

  7.             AdapterService service = getService();
  8.             if (service == null) return false;
  9.             return service.startDiscovery();
  10.         }
  11. }
3.1 server的
在./packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java中
  1. public class AdapterService extends Service {
  2. boolean startDiscovery() {
  3.         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, “Need BLUETOOTH ADMIN permission");
  4.         return startDiscoveryNative();
  5.     }
  6. }
会调用jni的startDiscoveryNative
4. jni中的调用
在./packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp中
  1. static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {
  2.     ALOGV("%s:",__FUNCTION__);

  3.     jboolean result = JNI_FALSE;
  4.     if (!sBluetoothInterface) return result;

  5.     int ret = sBluetoothInterface->start_discovery();
  6.     result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
  7.     return result;
  8. }
sBluetoothInterface是bluetooth.default.so的接口,即协议栈的接口


二.协议栈中的start_discovery
到了协议栈了,协议栈是在./external/bluetooth/bluedroid/中
2.1在./btif/src/bluetooth.c中
  1. static int start_discovery(void)
  2. {
  3.     /* sanity check */
  4.     if (interface_ready() == FALSE)
  5.         return BT_STATUS_NOT_READY;

  6.     return btif_dm_start_discovery();
  7. }
2.2在btif/src/btif_dm.c中
  1. bt_status_t btif_dm_start_discovery(void)
  2. {
  3.     tBTA_DM_INQ inq_params;
  4.     tBTA_SERVICE_MASK services = 0;
  5.     inq_params.mode = BTA_DM_GENERAL_INQUIRY;
  6.     inq_params.duration = BTIF_DM_DEFAULT_INQ_MAX_DURATION;
  7.     inq_params.max_resps = BTIF_DM_DEFAULT_INQ_MAX_RESULTS;
  8.     inq_params.report_dup = TRUE;
  9.     inq_params.filter_type = BTA_DM_INQ_CLR;
  10.     btif_dm_inquiry_in_progress = FALSE;
  11.     BTA_DmSearch(&inq_params, services, bte_search_devices_evt);
  12.     return BT_STATUS_SUCCESS;
  13. }
2.3在bta/dm/bta_dm_api.c中
  1. void BTA_DmSearch(tBTA_DM_INQ *p_dm_inq, tBTA_SERVICE_MASK services, tBTA_DM_SEARCH_CBACK *p_cback)
  2. {
  3.     tBTA_DM_API_SEARCH *p_msg;
  4.     if ((p_msg = (tBTA_DM_API_SEARCH *) GKI_getbuf(sizeof(tBTA_DM_API_SEARCH))) != NULL)
  5.     {
  6.         memset(p_msg, 0, sizeof(tBTA_DM_API_SEARCH));
  7.         p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;
  8.         memcpy(&p_msg->inq_params, p_dm_inq, sizeof(tBTA_DM_INQ));  //将这个inq复制到了inq_params中
  9.         p_msg->services = services;
  10.         p_msg->p_cback = p_cback;
  11.         p_msg->rs_res = BTA_DM_RS_NONE;
  12.         bta_sys_sendmsg(p_msg);
  13.     }
  14. }
其作用是调用bta_sys_sendmsg是向btu_task发送一个命令:p_msg->hdr.event = BTA_DM_API_SEARCH_EVT,参数是:inq_params
2.4 在stack/btu/btu_task.c中
  1. BTU_API UINT32 btu_task (UINT32 param)
  2. {
  3.     event = GKI_wait (0xFFFF, 0);  //从GKI_wait返回的event是4,这个不重要不管它了
  4.     if (event & TASK_MBOX_2_EVT_MASK)
  5.         while ((p_msg = (BT_HDR *) GKI_read_mbox(TASK_MBOX_2)) != NULL) //从这里面读取的才是真正发送的msg
  6.            bta_sys_event(p_msg);   //进行msg的处理,这里的msg才是BTA_DmSearch中发送的msg
  7. }
2.5在bta/sys/bta_sys_main.c中
  1. BTA_API void bta_sys_event(BT_HDR *p_msg)
  2. {
  3.     id = (UINT8) (p_msg->event >> 8);
  4.     if ((id < BTA_ID_MAX) && (bta_sys_cb.reg[id] != NULL))
  5.     {
  6.         freebuf = (*bta_sys_cb.reg[id]->evt_hdlr)(p_msg);
  7.     }
  8. }
又一个函数指针数组,那么这个evt_hdlr是在哪个地方初始化的呢?
2.6.evt_hdlr的初始化
bta/dm/bta_dm_api.c
  1. static const tBTA_SYS_REG bta_dm_search_reg =
  2. {
  3.     bta_dm_search_sm_execute,
  4.     bta_dm_search_sm_disable
  5. };

  6. tBTA_STATUS BTA_EnableBluetooth(tBTA_DM_SEC_CBACK *p_cback)
  7. {
  8.     bta_sys_register (BTA_ID_DM_SEARCH, &bta_dm_search_reg );
  9. }
下面就进入函数bta_dm_search_sm_execute
2.7 在bta/dm/bta_dm_main.c中,此时的msg还是BTA_DmSearch中发送的msg
  1. BOOLEAN bta_dm_search_sm_execute(BT_HDR *p_msg)
  2. {
  3.     tBTA_DM_ST_TBL state_table;
  4.     UINT8 action;
  5.     int i;
  6.     state_table = bta_dm_search_st_tbl[bta_dm_search_cb.state];
  7.     bta_dm_search_cb.state = state_table[p_msg->event & 0x00ff][BTA_DM_SEARCH_NEXT_STATE];

  8.     for (= 0; i < BTA_DM_SEARCH_ACTIONS; i++)
  9.     {
  10.         if ((action = state_table[p_msg->event & 0x00ff][i]) != BTA_DM_SEARCH_IGNORE)
  11.         {
  12.             (*bta_dm_search_action[action])( (tBTA_DM_MSG*) p_msg); //为什么要搞得这么复杂?
  13.         }
  14.         else
  15.         {
  16.             break;
  17.         }
  18.     }
  19.     return TRUE;
  20. }
这个函数指针数组看起来很复杂,但实际执行起来就是调用了bta_dm_search_action[0],
即: bta_dm_search_start
2.8 在bta/dm/bta_dm_act.c中
  1. void bta_dm_search_start (tBTA_DM_MSG *p_data)
  2. {
  3.     bta_dm_search_cb.p_search_cback = p_data->search.p_cback;
  4.     bta_dm_search_cb.services = p_data->search.services;

  5.     result.status = BTM_StartInquiry( (tBTM_INQ_PARMS*)&p_data->search.inq_params,
  6.                         bta_dm_inq_results_cb,
  7.                         (tBTM_CMPL_CB*) bta_dm_inq_cmpl_cb);

  8.     if (result.status != BTM_CMD_STARTED)
  9.     {
  10.         result.num_resp = 0;
  11.         bta_dm_inq_cmpl_cb ((void *)&result);
  12.     }
  13. }
a.发送的msg是一个tBTA_DM_API_SEARCH结构体,
而tBTA_DM_API_SEARCH结构体又属于tBTA_DM_MSG(enum)类型的search,
所以这儿就用了p_data->search来代替msg,但这个msg所指的内容,还是BTA_DmSearch所发送的msg
b. 同时BTM_StartInquiry中的第一个参数中inq_params,也就是btif_dm_start_discovery发送的参数
2.9 在stack/btm/btm_inq.c中
  1. tBTM_STATUS BTM_StartInquiry (tBTM_INQ_PARMS *p_inqparms, tBTM_INQ_RESULTS_CB *p_results_cb,
  2.                               tBTM_CMPL_CB *p_cmpl_cb)
  3. {
  4.     tBTM_STATUS status;
  5.     tBTM_INQUIRY_VAR_ST *p_inq = &btm_cb.btm_inq_vars;
  6.  
  7.     if (p_inq->inq_active || p_inq->inqfilt_active)
  8.         return (BTM_BUSY);

  9.     if (!BTM_IsDeviceUp())
  10.         return (BTM_WRONG_MODE);

  11.     if ((p_inqparms->mode & BTM_BR_INQUIRY_MASK)!= BTM_GENERAL_INQUIRY &&
  12.         (p_inqparms->mode & BTM_BR_INQUIRY_MASK)!= BTM_LIMITED_INQUIRY)
  13.         return (BTM_ILLEGAL_VALUE);

  14.     p_inq->inqparms = *p_inqparms;
  15.     p_inq->inqparms.mode = (UINT8)(<< (p_inqparms->mode & BTM_BR_INQUIRY_MASK));
  16.     p_inq->state = BTM_INQ_ACTIVE_STATE;
  17.     p_inq->p_inq_cmpl_cb = p_cmpl_cb;
  18.     p_inq->p_inq_results_cb = p_results_cb;
  19.     p_inq->inq_cmpl_info.num_resp = 0; /* Clear the results counter */
  20.     p_inq->inq_active = (UINT8)(<< (p_inqparms->mode & BTM_BR_INQUIRY_MASK));

  21.     switch (p_inqparms->filter_cond_type)
  22.     {
  23.     case BTM_CLR_INQUIRY_FILTER:
  24.         p_inq->state = BTM_INQ_SET_FILT_STATE;
  25.         break;

  26.     case BTM_FILTER_COND_DEVICE_CLASS:
  27.     case BTM_FILTER_COND_BD_ADDR:
  28.         p_inq->state = BTM_INQ_CLR_FILT_STATE;
  29.         p_inqparms->filter_cond_type = BTM_CLR_INQUIRY_FILTER;
  30.         break;

  31.     default:
  32.         return (BTM_ILLEGAL_VALUE);
  33.     }

  34.     if ((status = btm_set_inq_event_filter (p_inqparms->filter_cond_type, &p_inqparms->filter_cond)) != BTM_CMD_STARTED)
  35.         p_inq->state = BTM_INQ_INACTIVE_STATE;
  36.     return (status);
  37. }
3.0 与/dev/ttyS0进行通信
btm_set_inq_event_filter
    -->btsnd_hcic_set_event_filter
        -->btu_hcif_send_cmd
            --> HCI_CMD_TO_LOWER  
            --> bte_main_hci_send    // 发送了一个msg
     --> bt_hc_worker_thread    //接收者
        --> hci_h4_send_msg     //向/dev/ttyS2写入
  1. void hci_h4_send_msg(HC_BT_HDR *p_msg)
  2. { 
  3.     bytes_to_send = p_msg->len + 1;
  4.     //下面就是向/dev/ttyS2写入msg 
  5.     bytes_sent = userial_write(event,(uint8_t *) p, bytes_to_send);
  6.     return;
  7. }
当/dev/ttyS2有数据返回时,其监听线程会从select_read中返回
在hci/src/userial.c中
  1. static void *userial_read_thread(void *arg)
  2. {
  3.     while (userial_running)
  4.     {
  5.        rx_length = select_read(userial_cb.fd, p, READ_LIMIT);
  6.         if (rx_length > 0)
  7.         {
  8.             p_buf->len = (uint16_t)rx_length;
  9.             utils_enqueue(&(userial_cb.rx_q), p_buf);
  10.             bthc_signal_event(HC_EVENT_RX);
  11.         }        
  12.     }   
  13.     return NULL; // Compiler friendly
  14. }
 select_read不仅监听,当有数据时还会读取数据,当数据读取后会返回,
并用信号HC_EVENT_RX唤醒线程bt_hc_worker_thread
在hci/src/bt_hci_bdroid.c中
  1. static void *bt_hc_worker_thread(void *arg)
  2. { 
  3.     if (events & HC_EVENT_RX)
  4.        p_hci_if->rcv();
  5. }
又会调用hci/src/hci_h4.c中的hci_h4_receive_msg
当hci_h4_receive_msg中接收完所有的msg之后
  1. uint16_t hci_h4_receive_msg(void)
  2. {
  3.     if (msg_received)
  4.     {
  5.         if ((bt_hc_cbacks) && (intercepted == FALSE))
  6.          bt_hc_cbacks->data_ind((TRANSAC) p_cb->p_rcv_msg, \
  7.          (char *) (p_cb->p_rcv_msg + 1), p_cb->p_rcv_msg->len + BT_HC_HDR_SIZE);
  8.     }
  9. }
这个bt_hc_cbacks->data_ind就是调用main/bte_main.c中的data_ind
而main/bte_main.c中的data_ind就是发送了一个GKI_send_msg给BTU_TASK
在stack/./btu/btu_task.c中btu_task
btu_task收到了这个BT_EVT_TO_BTU_HCI_EVT,然后会调用
stack/./btu/btu_hcif.c:btu_hcif_process_event
    --> btu_hcif_command_status_evt   //当收到状态改变消息时会调用这个
    --> btu_hcif_inquiry_rssi_result_evt  //当查找到设备时会调用这个(buf中是以字符串形式存着:mac地址,类型等信息)
        --> btm_process_inq_results      //解析字符串,分析出bd_addr, dev_class等信息
            --> bta_dm_inq_results_cb     //bta/dm/bta_dm_act.c
                --> BTM_InqDbRead          //stack/btm/btm_inq.c 在记录的database中查看这个mac的记录是否存在
                --> 回调函数bte_search_devices_evt //这个就是在刚开始btif_dm_start_discovery时传来的回调函数           
在main/../btif/src/btif_dm.c中
  1. static void bte_search_devices_evt(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data)
  2. {
  3.     btif_transfer_context (btif_dm_search_devices_evt , (UINT16) event, (void *)p_data, param_len,
  4.         (param_len > sizeof(tBTA_DM_SEARCH)) ? search_devices_copy_cb : NULL);
  5. }
在./btif/src/btif_dm.c中是回调函数
  1. static void btif_dm_search_devices_evt (UINT16 event, char *p_param)
  2. {
  3.     btif_storage_add_remote_device(&bdaddr, num_properties, properties);
  4.     HAL_CBACK(bt_hal_cbacks, device_found_cb, num_properties, properties);
  5. }
把数据保存在cfg中,但这时候还不会写入/data/misc/bluedroid/bt_config.xml中,当与设备进行connect之后才会写入
最后调用HAL_CBACK,即调用./packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp中的函数,
./packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp又会调用java层的方法。
然后在CachedBluetoothDevice中添加了一个设备




附一: 与上层的交互
btif_dm_cb_create_bond时,会有状态变化:由原先的BT_BOND_STATE_NONE变为BT_BOND_STATE_BONDING
此时会调用bond_state_changed,上层就会收到bond_state_changed的消息,下面详细分析一下:
1.1 在btif/src/btif_dm.c中
    bond_state_changed(BT_STATUS_SUCCESS, bd_addr, BT_BOND_STATE_BONDING);
  1. static void bond_state_changed(bt_status_t status, bt_bdaddr_t *bd_addr, bt_bond_state_t state)
  2. {
  3.     HAL_CBACK(bt_hal_cbacks, bond_state_changed_cb, status, bd_addr, state);
  4. }
1.2 其中HAL_CBACK是一个宏
  1. #define HAL_CBACK(P_CB, P_CBACK, ...)\
  2.     if (P_CB && P_CB->P_CBACK) { \
  3.         P_CB->P_CBACK(__VA_ARGS__); \
  4.     } \
  5.     else { \
  6.         ASSERTC(0, "Callback is NULL", 0); \
  7.     }
这儿展开后: bt_hal_cbacks->bond_state_changed_cb(status, bd_addr, state);
其中status=BT_STATUS_SUCCESS,说明通知上层状态改变成功
bd_addr: 要pair的设备mac地址
state: 改变后的状态BT_BOND_STATE_BONDING
1.3 bt_hal_cbacks是如何初始化的呢?
来到bt_hal_cbacks的初始化函数中
在btif/src/bluetooth.c中
  1. static int init(bt_callbacks_t* callbacks )
  2. {
  3.     /* store reference to user callbacks */
  4.     bt_hal_cbacks = callbacks;
  5. }
1.4这个callbacks是如何来的呢?答案是在apk的jni层调用的
在./packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp中
  1. static bool initNative(JNIEnv* env, jobject obj) {
  2.     sJniCallbacksObj = env->NewGlobalRef(env->GetObjectField(obj, sJniCallbacksField));
  3.     if (sBluetoothInterface) {
  4.         sBluetoothInterface->init(&sBluetoothCallbacks);  
  5.     return JNI_FALSE;
  6. }
上面这个init就是调用bluetooth.default.so中的init,其中sBluetoothCallbacks的定义
  1. bt_callbacks_t sBluetoothCallbacks = {
  2.     sizeof(sBluetoothCallbacks),
  3.     adapter_state_change_callback,
  4.     adapter_properties_callback,
  5.     remote_device_properties_callback,
  6.     device_found_callback,
  7.     discovery_state_changed_callback,
  8.     pin_request_callback,
  9.     ssp_request_callback,
  10.     bond_state_changed_callback,
  11.     acl_state_changed_callback,
  12.     callback_thread_event,
  13. };
1.5 
在./packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp中
  1. static void bond_state_changed_callback(bt_status_t status, bt_bdaddr_t *bd_addr,
  2.                                         bt_bond_state_t state) {
  3.     jbyteArray addr;
  4.     addr = callbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
  5.     
  6.     callbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *)bd_addr);
  7.     //这儿是c调用java的方法
  8.     callbackEnv->CallVoidMethod(sJniCallbacksObj, method_bondStateChangeCallback, (jint) status, addr, (jint)state);
  9.     checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
  10.     callbackEnv->DeleteLocalRef(addr);
  11. }
其中在classInitNative中被初始化:
method_bondStateChangeCallback = env->GetMethodID(jniCallbackClass, "bondStateChangeCallback", "(I[BI)V");
原来这儿是调用了java的bondStateChangeCallback这个方法。

1.6 java
在./packages/apps/Bluetooth/src/com/android/bluetooth/btservice/JniCallbacks.java中
  1. void bondStateChangeCallback(int status, byte[] address, int newState) {
  2.         mBondStateMachine.bondStateChangeCallback(status, address, newState);
  3.     }
1.7 
在 ./packages/apps/Bluetooth/src/com/android/bluetooth/btservice/BondStateMachine.java中
  1. void bondStateChangeCallback(int status, byte[] address, int newState) {
  2.         BluetoothDevice device = mRemoteDevices.getDevice(address);
  3.         infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device + " newState: " + newState);

  4.         Message msg = obtainMessage(BONDING_STATE_CHANGE);
  5.         msg.obj = device;

  6.         if (newState == BOND_STATE_BONDED)
  7.             msg.arg1 = BluetoothDevice.BOND_BONDED;
  8.         else if (newState == BOND_STATE_BONDING)
  9.             msg.arg1 = BluetoothDevice.BOND_BONDING;
  10.         else
  11.             msg.arg1 = BluetoothDevice.BOND_NONE;
  12.         msg.arg2 = status;

  13.         sendMessage(msg);
  14.     }

您可能感兴趣的与本文相关内容

<think> 我们正在扩展已有的分析功能,现在需要支持按时间范围和业务ID过滤异常调用链。 根据用户提供的需求,我们需要修改之前的代码,添加时间范围(start和end)和业务ID(businessId)作为查询条件。 原业务流程说明: 1. 从异常调用链表(索引模式:ete_data_fault_trace_*)中查询符合以下条件的调用链: - 入口服务在给定的入口服务列表中(由业务告警触发) - 最深故障节点类型在外部服务节点类型列表中(从数据字典获取) 2. 按spec.url分组统计异常数量 3. 计算每个url的异常占比(该url的异常数量 / 总异常数量),并与阈值比较 4. 将超过阈值的记录保存为分析结果 现在需要新增两个过滤条件: - 时间范围:start和end,用于过滤调用链的时间(假设索引中有字段记录时间,例如log_time) - 业务ID:businessId,用于关联具体的业务(假设索引中有字段business_id) 修改点: 1. 在构建基础查询时,新增时间范围和业务ID的过滤条件。 2. 注意:异常调用链索引按月分区,但时间范围可能跨月,因此索引模式已经使用了通配符(ete_data_fault_trace_*),所以不需要修改索引模式。 3. 在聚合查询中,需要在基础查询中增加时间范围和业务ID的条件。 假设索引中相关字段: - 时间字段:log_time (类型为date) - 业务ID字段:business_id (类型可能是long或keyword) 因此,我们需要修改buildBaseQuery方法,增加两个must条件。 另外,注意原方法签名修改为: public void analyse(LocalDateTime start, LocalDateTime end, Long businessId, List<String> entryServices, String analysisId) 但根据用户提供的参数,方法签名是:public void analyse(LocalDateTime start, LocalDateTime end, Long businessId) 因此我们需要考虑entryServices和analysisId的来源。根据上下文,entryServices应该由业务告警触发传入,analysisId是本次分析的任务ID。 由于用户没有提供entryServices和analysisId的来源,我们假设它们作为参数传入,因此方法签名可以调整为: public void analyse(LocalDateTime start, LocalDateTime end, Long businessId, List<String> entryServices, String analysisId) 但是,用户给出的方法签名只有三个参数:start, end, businessId。因此,我们需要明确: - 如果entryServices和analysisId在类中已有(例如通过构造方法传入或通过其他方式获取),则不需要作为参数。 - 但是,根据需求,entryServices是触发业务告警关联的入口服务,应该由调用方传入。analysisId同理。 考虑到用户给出的方法签名,我们可能有两种处理方式: 1. 调整方法签名,增加entryServices和analysisId参数。 2. 如果无法调整,则需要在类中增加字段,通过构造函数或setter方法设置。 由于问题中给出的方法签名是固定的,我们需要按照用户给出的签名来实现。但是,原需求中需要entryServices和analysisId,所以我们需要在类中存储这些信息。 因此,我们调整设计: 将entryServices和analysisId作为类的字段,在构造方法中传入,或者通过setter设置。这样analyse方法就可以只使用start, end, businessId作为参数。 但是,用户要求的方法签名是:public void analyse(LocalDateTime start, LocalDateTime end, Long businessId) 因此,我们需要在类中提前设置entryServices和analysisId。例如: public void setEntryServices(List<String> entryServices) { ... } public void setAnalysisId(String analysisId) { ... } 然后通过analyse方法使用这些字段。 然而,为了保持代码清晰,我们修改构造函数,让这些参数在构造时传入,然后在analyse方法中只使用时间范围和业务ID。 但是,用户要求的方法签名是固定的,所以我们只能按照: public void analyse(LocalDateTime start, LocalDateTime end, Long businessId) 那么entryServices和analysisId从哪里来?我们有以下选择: - 在调用analyse方法之前,通过setter设置(但用户没有提供setter的要求) - 或者,在需求中,entryServices和analysisId可能来源于其他途径(例如业务告警触发时已经关联了entryServices,而analysisId是生成的),但用户没有明确。 由于题目要求生成的方法签名为:public void analyse(LocalDateTime start, LocalDateTime end, Long businessId) 我们只能假设类中已经存储了entryServices和analysisId(例如在构造时传入)。所以我们修改类: 在类中增加成员变量:private List<String> entryServices; 和 private String analysisId; 通过构造函数传入:ExternalServiceFaultAnalyzer(ElasticsearchClient client, List<String> entryServices, String analysisId) 但是,这样entryServices和analysisId对于每次分析是固定的,但实际上每次分析可能不同。因此,更合理的是在每次分析时传入。 由于题目要求的方法签名只有三个参数,我们只能将entryServices和analysisId作为类成员,并在analyse方法中使用。 然而,这样设计会限制类的复用(因为每次分析需要创建新的实例)。所以,我们采用另一种方式:在analyse方法内部获取entryServices和analysisId。但是用户没有提供如何获取。 鉴于题目要求,我们暂且这样处理: - 将entryServices和analysisId作为类成员变量,通过构造方法初始化。 - 这样,在analyse方法中就可以直接使用。 因此,我们不需要改变analyse方法的签名,而是按照题目要求。 现在,我们修改代码: 步骤: 1. 在buildBaseQuery方法中,增加时间范围和业务ID的过滤条件。 2. 在聚合查询中,使用新的基础查询(包含时间范围和业务ID)。 注意:时间范围过滤使用RangeQuery,业务ID使用TermQuery(因为businessId是精确值)。 代码修改如下: 原buildBaseQuery方法只包含入口服务和最深故障节点类型的条件。现在增加两个must条件:时间范围和业务ID。 修改后的buildBaseQuery: private Query buildBaseQuery(LocalDateTime start, LocalDateTime end, Long businessId, List<String> thirdPartyTypes) { // 创建时间范围查询 Query rangeQuery = RangeQuery.of(r -> r .field("log_time") .gte(JsonData.of(start.toString())) .lte(JsonData.of(end.toString())) )._toQuery(); // 创建业务ID查询 Query businessIdQuery = TermQuery.of(t -> t .field("business_id") .value(businessId) )._toQuery(); return BoolQuery.of(bq -> bq .must(m -> m.terms(t -> t .field("entry_service.keyword") .terms(tv -> tv.value(entryServices.stream() .map(FieldValue::of).collect(Collectors.toList())))) ) .must(m -> m.terms(t -> t .field("deepest_fault_node_type.keyword") .terms(tv -> tv.value(thirdPartyTypes.stream() .map(FieldValue::of).collect(Collectors.toList())))) ) .must(rangeQuery) // 新增时间范围条件 .must(businessIdQuery) // 新增业务ID条件 )._toQuery(); } 注意:这里的时间格式问题。Elasticsearch中存储的时间格式可能与LocalDateTime的toString()格式不一致。需要确保格式一致,或者使用JsonData的日期转换。 通常,我们可以将LocalDateTime转换为ISO_LOCAL_DATE_TIME格式(例如"2023-10-01T12:00:00"),并确保Elasticsearch中log_time字段的格式与此兼容。 另外,在调用buildBaseQuery时,需要传入start, end, businessId和thirdPartyTypes。 因此,在analyzeExternalServiceFault方法(现在改名为analyse)中,需要修改基础查询的构建: // 原基础查询构建 // Query baseQuery = buildBaseQuery(entryServices, thirdPartyTypes); // 改为 Query baseQuery = buildBaseQuery(start, end, businessId, thirdPartyTypes); 注意:原方法analyzeExternalServiceFault现在改名为analyse,参数为(LocalDateTime start, LocalDateTime end, Long businessId) 因此,我们重构方法: public void analyse(LocalDateTime start, LocalDateTime end, Long businessId) throws Exception { // 获取配置 List<String> thirdPartyTypes = getDictValues("calltype_list_third"); double thresholdRate = Double.parseDouble(getDictValues("third_rate_threshold").get(0)); // 构建基础查询(新增时间范围和业务ID) Query baseQuery = buildBaseQuery(start, end, businessId, thirdPartyTypes); // ... 剩余代码不变 ... } 同时,删除原方法中的entryServices和analysisId参数,因为entryServices和analysisId已经是类成员变量。 但是,注意原方法中使用了entryServices(在buildBaseQuery中)和analysisId(在保存结果时)。所以,我们假设类中已经定义: private List<String> entryServices; // 在构造时传入 private String analysisId; // 在构造时传入 因此,构造方法需要修改: public ExternalServiceFaultAnalyzer(ElasticsearchClient client, List<String> entryServices, String analysisId) { this.client = client; this.entryServices = entryServices; this.analysisId = analysisId; } 这样,在analyse方法中就可以直接使用this.entryServices和this.analysisId。 最后,注意原方法中保存结果时使用的analysisId就是this.analysisId。 另外,由于我们新增了时间范围和业务ID条件,在获取callType的方法(fetchCallTypeForUrl)中也需要使用同样的基础查询(加上时间范围和业务ID)?但是注意,在fetchCallTypeForUrl中,我们是在基础查询(已经包含时间范围、业务ID、入口服务、故障节点类型)的基础上,再增加一个url条件。 所以fetchCallTypeForUrl方法也需要修改,将基础查询传入(现在基础查询已经包含时间范围和业务ID),但原方法已经传入baseQuery,所以不需要修改fetchCallTypeForUrl的调用。 因此,我们只需要修改buildBaseQuery方法,并调整analyse方法的实现。 下面给出修改后的完整代码: </think> 以下是针对您提供的`analyse`方法签名实现的完整代码,扩展了外部服务异常分析功能,支持时间范围(`start`, `end`)和业务ID(`businessId`)过滤: ```java import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.aggregations.*; import co.elastic.clients.elasticsearch._types.query_dsl.*; import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.json.JsonData; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; public class ExternalServiceFaultAnalyzer { private final ElasticsearchClient client; private final String faultTraceIndexPattern = "ete_data_fault_trace_*"; private List<String> entryServices; // 入口服务列表(由业务告警确定) private String analysisId; // 本次分析ID public ExternalServiceFaultAnalyzer(ElasticsearchClient client, List<String> entryServices, String analysisId) { this.client = client; this.entryServices = entryServices; this.analysisId = analysisId; } /** * 执行带时间范围和业务ID过滤的外部服务异常分析 * @param start 分析开始时间 * @param end 分析结束时间 * @param businessId 关联业务ID */ public void analyse(LocalDateTime start, LocalDateTime end, Long businessId) throws Exception { // 1. 从数据字典获取配置 List<String> thirdPartyTypes = getDictValues("calltype_list_third"); double thresholdRate = Double.parseDouble(getDictValues("third_rate_threshold").get(0)); // 2. 构建带时间范围的查询 Query baseQuery = buildBaseQuery(start, end, businessId, thirdPartyTypes); // 3. 聚合查询: 按spec.url分组 SearchResponse<Void> aggResponse = client.search(b -> b .index(faultTraceIndexPattern) .size(0) .query(baseQuery) .aggregations("by_url", a -> a .terms(t -> t.field("spec.url.keyword").size(1000) .aggregations("trace_ids", ta -> ta .terms(t2 -> t2.field("traceId.keyword").size(10000)) ) ) ), Void.class ); // 4. 处理聚合结果 List<AggregationBucket> buckets = Objects.requireNonNull(aggResponse.aggregations()) .get("by_url").sterms().buckets().array(); // 5. 计算异常占比并筛选故障项 long totalFaults = buckets.stream().mapToLong(AggregationBucket::docCount).sum(); List<FaultResult> faultResults = new ArrayList<>(); for (AggregationBucket bucket : buckets) { String url = bucket.key().stringValue(); long faultCount = bucket.docCount(); double faultRate = totalFaults > 0 ? (double) faultCount / totalFaults : 0; if (faultRate > thresholdRate) { // 获取traceId列表 List<String> traceIds = bucket.aggregations().get("trace_ids") .sterms().buckets().array().stream() .map(b -> b.key().stringValue()) .collect(Collectors.toList()); // 获取callType和bipCode(如果存在) Map<String, String> serviceInfo = fetchServiceInfo(url, baseQuery); String callType = serviceInfo.getOrDefault("callType", "UNKNOWN"); String bipCode = serviceInfo.get("bipCode"); // CBOSS接口标识 faultResults.add(new FaultResult( callType, url, faultCount, faultRate, analysisId, traceIds, bipCode, businessId )); } } // 6. 存储分析结果 saveToAnalyseResult(faultResults); } // 构建带时间范围和业务ID的基础查询 private Query buildBaseQuery(LocalDateTime start, LocalDateTime end, Long businessId, List<String> thirdPartyTypes) { // 时间范围过滤 Query timeRangeQuery = RangeQuery.of(r -> r .field("log_time") .gte(JsonData.of(start.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))) .lte(JsonData.of(end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))) )._toQuery(); // 业务ID过滤 Query businessIdQuery = TermQuery.of(t -> t .field("business_id") .value(businessId) )._toQuery(); return BoolQuery.of(bq -> bq .must(m -> m.terms(t -> t // 入口服务过滤 .field("entry_service.keyword") .terms(tv -> tv.value(entryServices.stream() .map(FieldValue::of).collect(Collectors.toList())))) ) .must(m -> m.terms(t -> t // 外部服务类型过滤 .field("deepest_fault_node_type.keyword") .terms(tv -> tv.value(thirdPartyTypes.stream() .map(FieldValue::of).collect(Collectors.toList())))) ) .must(timeRangeQuery) // 时间范围条件 .must(businessIdQuery) // 业务ID条件 )._toQuery(); } // 获取URL对应的服务信息 private Map<String, String> fetchServiceInfo(String url, Query baseQuery) throws Exception { SearchResponse<Map> response = client.search(sb -> sb .index(faultTraceIndexPattern) .size(1) .query(q -> q .bool(b -> b .must(baseQuery) .must(m -> m.match(mt -> mt.field("spec.url.keyword").query(url))) ) ) .source(sf -> sf.filter(f -> f.includes("callType", "bipCode"))), Map.class ); Map<String, String> result = new HashMap<>(); if (!response.hits().hits().isEmpty()) { Map<String, Object> source = response.hits().hits().get(0).source(); if (source != null) { result.put("callType", source.getOrDefault("callType", "").toString()); if (source.containsKey("bipCode")) { result.put("bipCode", source.get("bipCode").toString()); } } } return result; } // 从数据字典获取配置值(伪实现) private List<String> getDictValues(String dictType) { // 实际实现需查询sys_dict索引 if ("calltype_list_third".equals(dictType)) { return Arrays.asList("HTTP-CLIENT", "OKHTTP", "GRPC"); } return Collections.singletonList("0.6"); // 默认阈值60% } // 存储分析结果(伪实现) private void saveToAnalyseResult(List<FaultResult> results) { // 实际写入ete_data_analyse_result索引 results.forEach(r -> { String bipInfo = r.bipCode != null ? " | bipCode: " + r.bipCode : ""; System.out.printf("[业务ID:%d] 故障类型: %s | URL: %s | 异常次数: %d | 异常占比: %.2f%s%n", r.businessId, r.callType, r.url, r.faultCount, r.faultRate, bipInfo); }); } // 结果封装类 static class FaultResult { String callType; String url; long faultCount; double faultRate; String analysisId; List<String> traceIds; String bipCode; // CBOSS接口标识 Long businessId; // 关联业务ID public FaultResult(String callType, String url, long faultCount, double faultRate, String analysisId, List<String> traceIds, String bipCode, Long businessId) { this.callType = callType; this.url = url; this.faultCount = faultCount; this.faultRate = faultRate; this.analysisId = analysisId; this.traceIds = traceIds; this.bipCode = bipCode; this.businessId = businessId; } } } ``` ### 关键增强说明: 1. **时间范围过滤** 使用`RangeQuery`处理`log_time`字段,支持ISO 8601时间格式: ```java RangeQuery.of(r -> r .field("log_time") .gte(start.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) .lte(end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) ) ``` 2. **业务ID精确匹配** 通过`TermQuery`实现业务ID过滤: ```java TermQuery.of(t -> t .field("business_id") .value(businessId) ) ``` 3. **CBOSS接口级定位** 在`fetchServiceInfo()`中主动获取`bipCode`字段: ```java .source(sf -> sf.filter(f -> f.includes("callType", "bipCode"))) ``` 4. **结果对象扩展** `FaultResult`新增`bipCode`和`businessId`字段,支持CBOSS细粒度定位和业务关联 5. **安全计算** 异常占比计算增加分母为零保护: ```java double faultRate = totalFaults > 0 ? (double) faultCount / totalFaults : 0; ``` ### 使用示例: ```java // 初始化分析器 (入口服务列表来自业务告警) List<String> alertServices = Arrays.asList("orderService", "paymentService"); ExternalServiceFaultAnalyzer analyzer = new ExternalServiceFaultAnalyzer( esClient, alertServices, "ANALYSIS-202310" ); // 执行业务ID=12345在指定时间范围的分析 LocalDateTime start = LocalDateTime.of(2023, 10, 1, 0, 0); LocalDateTime end = LocalDateTime.of(2023, 10, 15, 23, 59); analyzer.analyse(start, end, 12345L); ```
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值