| @@ -81,45 +81,102 @@ struct CCUDelayChannel | |||
| 81 | uint16_t channelId = UINT16_MAX; | 81 | uint16_t channelId = UINT16_MAX; |
| 82 | }; | 82 | }; |
| 83 | 83 | ||
| 84 | +struct CCUChannelPrefixInfo | ||
| 85 | +{ | ||
| 86 | + uint64_t timestamp = UINT64_MAX; | ||
| 87 | + uint64_t maxDelay = 0; | ||
| 88 | + uint16_t channelId = UINT16_MAX; | ||
| 89 | +}; | ||
| 90 | + | ||
| 91 | +using CCUChannelIndex = std::unordered_map<uint16_t, std::vector<CCUChannelPrefixInfo>>; | ||
| 92 | + | ||
| 84 | std::string MakeCCUKey(uint64_t streamId, uint64_t taskId, uint64_t instrId) | 93 | std::string MakeCCUKey(uint64_t streamId, uint64_t taskId, uint64_t instrId) |
| 85 | { | 94 | { |
| 86 | return std::to_string(streamId) + "_" + std::to_string(taskId) + "_" + std::to_string(instrId); | 95 | return std::to_string(streamId) + "_" + std::to_string(taskId) + "_" + std::to_string(instrId); |
| 87 | } | 96 | } |
| 88 | 97 | ||
| 89 | CCUDelayChannel GetMaxDelayChannel(const std::vector<CCUWaitSignalInfo> &hostData, const CCUMissionInfo &missionData, | 98 | CCUDelayChannel GetMaxDelayChannel(const std::vector<CCUWaitSignalInfo> &hostData, const CCUMissionInfo &missionData, |
| 90 | - const std::vector<CCUChannelInfo> &channelData) | 99 | + const CCUChannelIndex &channelIndex) |
| 91 | { | 100 | { |
| 92 | - if (channelData.empty()) | 101 | + if (channelIndex.empty()) |
| 93 | { | 102 | { |
| 94 | return {}; | 103 | return {}; |
| 95 | } | 104 | } |
| 96 | std::unordered_set<uint16_t> hostChannelIds; | 105 | std::unordered_set<uint16_t> hostChannelIds; |
| 97 | - for (const auto &item : hostData) | 106 | + hostChannelIds.reserve(hostData.size()); |
| 98 | - { | ||
| 99 | - hostChannelIds.insert(item.channelId); | ||
| 100 | - } | ||
| 101 | CCUDelayChannel maxDelay; | 107 | CCUDelayChannel maxDelay; |
| 102 | bool found = false; | 108 | bool found = false; |
| 103 | - for (const auto &channel : channelData) | 109 | + for (const auto &item : hostData) |
| 104 | { | 110 | { |
| 105 | - if (hostChannelIds.find(channel.channelId) == hostChannelIds.end()) | 111 | + if (!hostChannelIds.insert(item.channelId).second) |
| 106 | { | 112 | { |
| 107 | continue; | 113 | continue; |
| 108 | } | 114 | } |
| 109 | - if (channel.timestamp >= missionData.endTime) | 115 | + auto channelIter = channelIndex.find(item.channelId); |
| 116 | + if (channelIter == channelIndex.end()) | ||
| 110 | { | 117 | { |
| 111 | continue; | 118 | continue; |
| 112 | } | 119 | } |
| 113 | - if (!found || channel.avgDelay > maxDelay.channelDelay) | 120 | + const auto &channelRecords = channelIter->second; |
| 121 | + auto recordIter = std::lower_bound(channelRecords.begin(), channelRecords.end(), missionData.endTime, | ||
| 122 | + [](const CCUChannelPrefixInfo &record, uint64_t endTime) | ||
| 123 | + { return record.timestamp < endTime; }); | ||
| 124 | + if (recordIter == channelRecords.begin()) | ||
| 114 | { | 125 | { |
| 115 | - maxDelay.channelId = channel.channelId; | 126 | + continue; |
| 116 | - maxDelay.channelDelay = channel.avgDelay; | 127 | + } |
| 128 | + --recordIter; | ||
C | |||
| 129 | + if (!found || recordIter->maxDelay > maxDelay.channelDelay) | ||
| 130 | + { | ||
| 131 | + maxDelay.channelId = recordIter->channelId; | ||
| 132 | + maxDelay.channelDelay = recordIter->maxDelay; | ||
| 117 | found = true; | 133 | found = true; |
| 118 | } | 134 | } |
| 119 | } | 135 | } |
| 120 | return maxDelay; | 136 | return maxDelay; |
| 121 | } | 137 | } |
| 122 | 138 | ||
| 139 | +CCUChannelIndex BuildChannelIndex(const std::vector<CCUChannelInfo> &channelInfo) | ||
| 140 | +{ | ||
| 141 | + std::unordered_map<uint16_t, std::vector<CCUChannelInfo>> groupedChannelInfo; | ||
| 142 | + groupedChannelInfo.reserve(channelInfo.size()); | ||
[Review] groupedChannelInfo 实际按唯一的 channelId 数分组,当 channelInfo 中重复的 channelId 较多时,以 channelId 的 size 预留 groupedChannelInfo 的 size 可能导致内存空间占用过多,建议确认实际是否有这种场景 ![]() ![]() | |||
| 143 | + for (const auto &channel : channelInfo) | ||
| 144 | + { | ||
| 145 | + groupedChannelInfo[channel.channelId].emplace_back(channel); | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + CCUChannelIndex channelIndex; | ||
| 149 | + channelIndex.reserve(groupedChannelInfo.size()); | ||
| 150 | + for (auto &item : groupedChannelInfo) | ||
| 151 | + { | ||
| 152 | + auto &records = item.second; | ||
| 153 | + std::sort(records.begin(), records.end(), [](const CCUChannelInfo &left, const CCUChannelInfo &right) | ||
| 154 | + { return left.timestamp < right.timestamp; }); | ||
| 155 | + auto &prefixRecords = channelIndex[item.first]; | ||
| 156 | + if (!Reserve(prefixRecords, records.size())) | ||
P [review] BuildChannelIndex 中 Reserve 失败后留下空条目 问题现象: channelIndex[item.first] 通过 operator[] 访问会先创建一个空 vector 条目,然后才调用 Reserve。如果 Reserve 返回 false 执行 continue,该空条目会残留在 channelIndex 中。虽然后续 GetMaxDelayChannel 对空 vector 有 recordIter == channelRecords.begin() 的保护不会崩溃,但这会在索引中留下无效的"脏数据",且 channelIndex.find(item.channelId) 会错误地命中该空条目后白白执行一次无意义的查找。 修改意见:
将 Reserve 检查前置,或先检查再插入。建议改为:
std::vector ![]() ![]() | |||
| 157 | + { | ||
| 158 | + ERROR("Reserve ccu channel index failed."); | ||
| 159 | + continue; | ||
| 160 | + } | ||
| 161 | + uint64_t maxDelay = 0; | ||
| 162 | + uint16_t maxDelayChannel = UINT16_MAX; | ||
| 163 | + for (const auto &record : records) | ||
| 164 | + { | ||
| 165 | + if (record.avgDelay > maxDelay) | ||
| 166 | + { | ||
| 167 | + maxDelay = record.avgDelay; | ||
| 168 | + maxDelayChannel = record.channelId; | ||
| 169 | + } | ||
| 170 | + CCUChannelPrefixInfo prefixRecord; | ||
| 171 | + prefixRecord.timestamp = record.timestamp; | ||
| 172 | + prefixRecord.maxDelay = maxDelay; | ||
| 173 | + prefixRecord.channelId = maxDelayChannel; | ||
| 174 | + prefixRecords.emplace_back(prefixRecord); | ||
| 175 | + } | ||
| 176 | + } | ||
| 177 | + return channelIndex; | ||
| 178 | +} | ||
| 179 | + | ||
| 123 | void ConvertMissionData(const OriCCUMissionData &oriData, std::vector<CCUMissionInfo> &missionData) | 180 | void ConvertMissionData(const OriCCUMissionData &oriData, std::vector<CCUMissionInfo> &missionData) |
| 124 | { | 181 | { |
| 125 | if (!Reserve(missionData, oriData.size())) | 182 | if (!Reserve(missionData, oriData.size())) |
| @@ -258,6 +315,7 @@ void FormatWaitTimelineData(const std::vector<CCUMissionInfo> &waitData, | |||
| 258 | } | 315 | } |
| 259 | std::unordered_map<std::string, std::vector<CCUMissionInfo>> groupedWaitData; | 316 | std::unordered_map<std::string, std::vector<CCUMissionInfo>> groupedWaitData; |
| 260 | std::unordered_map<std::string, std::vector<CCUWaitSignalInfo>> groupedWaitSignalData; | 317 | std::unordered_map<std::string, std::vector<CCUWaitSignalInfo>> groupedWaitSignalData; |
| 318 | + auto channelIndex = BuildChannelIndex(channelInfo); | ||
| 261 | for (const auto &item : waitData) | 319 | for (const auto &item : waitData) |
| 262 | { | 320 | { |
| 263 | auto key = MakeCCUKey(item.streamId, item.taskId, item.setCkeBitInstrId); | 321 | auto key = MakeCCUKey(item.streamId, item.taskId, item.setCkeBitInstrId); |
| @@ -298,7 +356,7 @@ void FormatWaitTimelineData(const std::vector<CCUMissionInfo> &waitData, | |||
| 298 | traceData.dieId = hostData.dieId; | 356 | traceData.dieId = hostData.dieId; |
| 299 | traceData.hasMask = true; | 357 | traceData.hasMask = true; |
| 300 | traceData.mask = hostData.mask; | 358 | traceData.mask = hostData.mask; |
| 301 | - auto maxDelay = GetMaxDelayChannel(hostDataIter->second, data, channelInfo); | 359 | + auto maxDelay = GetMaxDelayChannel(hostDataIter->second, data, channelIndex); |
| 302 | if (maxDelay.channelId != UINT16_MAX && maxDelay.channelDelay != 0) | 360 | if (maxDelay.channelId != UINT16_MAX && maxDelay.channelDelay != 0) |
| 303 | { | 361 | { |
| 304 | traceData.hasDelayChannel = true; | 362 | traceData.hasDelayChannel = true; |
| @@ -50,11 +50,13 @@ class CCUMissionViewer(BaseViewer, ABC): | |||
| 50 | DataTag.CCU_CHANNEL: (CCUViewerChannelModel, ccu_device_path), | 50 | DataTag.CCU_CHANNEL: (CCUViewerChannelModel, ccu_device_path), |
| 51 | } | 51 | } |
| 52 | if os.path.exists(ccu_add_info_path): | 52 | if os.path.exists(ccu_add_info_path): |
| 53 | - self.model_list.update({ | 53 | + self.model_list.update( |
| 54 | - DataTag.CCU_TASK: (CCUViewerTaskInfoModel, ccu_add_info_path), | 54 | + { |
| 55 | - DataTag.CCU_WAIT_SIGNAL: (CCUViewerWaitSignalInfoModel, ccu_add_info_path), | 55 | + DataTag.CCU_TASK: (CCUViewerTaskInfoModel, ccu_add_info_path), |
| 56 | - DataTag.CCU_GROUP: (CCUViewerGroupInfoModel, ccu_add_info_path) | 56 | + DataTag.CCU_WAIT_SIGNAL: (CCUViewerWaitSignalInfoModel, ccu_add_info_path), |
| 57 | - }) | 57 | + DataTag.CCU_GROUP: (CCUViewerGroupInfoModel, ccu_add_info_path), |
| 58 | + } | ||
| 59 | + ) | ||
| 58 | self.pid = InfoConfReader().get_json_pid_data() | 60 | self.pid = InfoConfReader().get_json_pid_data() |
| 59 | self.tid = InfoConfReader().get_json_tid_data() | 61 | self.tid = InfoConfReader().get_json_tid_data() |
| 60 | 62 | ||
| @@ -62,26 +64,36 @@ class CCUMissionViewer(BaseViewer, ABC): | |||
| 62 | def format_mission_summary_data(summary_data: list) -> list: | 64 | def format_mission_summary_data(summary_data: list) -> list: |
| 63 | return [ | 65 | return [ |
| 64 | ( | 66 | ( |
| 65 | - data.stream_id, data.task_id, data.lp_instr_id, | 67 | + data.stream_id, |
| 68 | + data.task_id, | ||
| 69 | + data.lp_instr_id, | ||
| 66 | format_high_precision_for_csv(InfoConfReader().trans_syscnt_into_local_time(data.lp_start_time)), | 70 | format_high_precision_for_csv(InfoConfReader().trans_syscnt_into_local_time(data.lp_start_time)), |
| 67 | format_high_precision_for_csv( | 71 | format_high_precision_for_csv( |
| 68 | - str(InfoConfReader().duration_from_syscnt(data.lp_end_time - data.lp_start_time))), | 72 | + str(InfoConfReader().duration_from_syscnt(data.lp_end_time - data.lp_start_time)) |
| 69 | - data.setckebit_instr_id, data.rel_id, | 73 | + ), |
| 74 | + data.setckebit_instr_id, | ||
| 75 | + data.rel_id, | ||
| 70 | format_high_precision_for_csv( | 76 | format_high_precision_for_csv( |
| 71 | - str(InfoConfReader().duration_from_syscnt(data.rel_end_time - data.setckebit_start_time))) | 77 | + str(InfoConfReader().duration_from_syscnt(data.rel_end_time - data.setckebit_start_time)) |
| 72 | - ) for data in summary_data | 78 | + ), |
| 79 | + ) | ||
| 80 | + for data in summary_data | ||
| 73 | ] | 81 | ] |
| 74 | 82 | ||
| 75 | 83 | ||
| 76 | - def get_max_delay_channel_and_channel_delay(host_data: list, mission_data: any, channel_data: list) -> any: | 84 | + def get_max_delay_channel_and_channel_delay(host_data: list, mission_data: any, channel_data: dict) -> any: |
| 77 | if not channel_data: | 85 | if not channel_data: |
| 78 | return None, None | 86 | return None, None |
| 79 | host_channel_ids = {channel.channel_id for channel in host_data} | 87 | host_channel_ids = {channel.channel_id for channel in host_data} |
| 80 | - within_channel = [channel for channel in channel_data if channel.channel_id in host_channel_ids] | 88 | + |
| 81 | - seq_channel = [channel | 89 | + within_channel = [] |
| 82 | - for channel in within_channel | 90 | + for host_channel_id in host_channel_ids: |
| 83 | - if channel.timestamp < mission_data.end_time | 91 | + if host_channel_id in channel_data: |
| 84 | - ] | 92 | + within_channel.extend(channel_data[host_channel_id]) |
| 93 | + if not within_channel: | ||
| 94 | + return None, None | ||
| 95 | + | ||
| 96 | + seq_channel = [channel for channel in within_channel if channel.timestamp < mission_data.end_time] | ||
| 85 | if seq_channel: | 97 | if seq_channel: |
| 86 | max_delay_channel = max(seq_channel, key=lambda x: x.avg_bw) | 98 | max_delay_channel = max(seq_channel, key=lambda x: x.avg_bw) |
C 【review】【设计】Python 端的 get_max_delay_channel_and_channel_delay 仍使用 max(seq_channel, key=lambda x: x.avg_bw) 计算最大延迟通道,但 avg_bw 是平均带宽,不是延迟(delay)。字段名 avg_bw 与语义 max_delay_channel 不符。,建议修改:应该使用 avg_delay 而非 avg_bw 来计算最大延迟通道 ![]() ![]() | |||
| 87 | return max_delay_channel.channel_id, max_delay_channel.avg_bw | 99 | return max_delay_channel.channel_id, max_delay_channel.avg_bw |
| @@ -89,11 +101,8 @@ class CCUMissionViewer(BaseViewer, ABC): | |||
| 89 | 101 | ||
| 90 | def get_timeline_header(self) -> list: | 102 | def get_timeline_header(self) -> list: |
| 91 | header = [ | 103 | header = [ |
| 92 | - ["process_name", | 104 | + ["process_name", self.pid, self.tid, TraceViewHeaderConstant.PROCESS_CCU], |
| 93 | - self.pid, self.tid, | 105 | + ["thread_name", self.pid, self.tid, TraceViewHeaderConstant.PROCESS_COMMUNICATION], |
| 94 | - TraceViewHeaderConstant.PROCESS_CCU], | ||
| 95 | - ["thread_name", | ||
| 96 | - self.pid, self.tid, TraceViewHeaderConstant.PROCESS_COMMUNICATION], | ||
| 97 | ] | 106 | ] |
| 98 | return TraceViewManager.metadata_event(header) | 107 | return TraceViewManager.metadata_event(header) |
| 99 | 108 | ||
| @@ -149,19 +158,17 @@ class CCUMissionViewer(BaseViewer, ABC): | |||
| 149 | ccu_mission_data = ccu_data_dict.get(DataTag.CCU_MISSION, []) | 158 | ccu_mission_data = ccu_data_dict.get(DataTag.CCU_MISSION, []) |
| 150 | loop_data = [data for data in ccu_mission_data if data.time_type == 'LoopGroup'] | 159 | loop_data = [data for data in ccu_mission_data if data.time_type == 'LoopGroup'] |
| 151 | wait_data = [data for data in ccu_mission_data if data.time_type == 'Wait'] | 160 | wait_data = [data for data in ccu_mission_data if data.time_type == 'Wait'] |
| 152 | - result.extend(self.get_formatted_loop_data( | 161 | + result.extend(self.get_formatted_loop_data(loop_data, ccu_data_dict.get(DataTag.CCU_GROUP, []))) |
| 153 | - loop_data, | 162 | + result.extend( |
| 154 | - ccu_data_dict.get(DataTag.CCU_GROUP, []) | 163 | + self.get_formatted_wait_data( |
| 155 | - )) | 164 | + wait_data, ccu_data_dict.get(DataTag.CCU_WAIT_SIGNAL, []), ccu_data_dict.get(DataTag.CCU_CHANNEL, []) |
| 156 | - result.extend(self.get_formatted_wait_data( | 165 | + ) |
| 157 | - wait_data, | 166 | + ) |
| 158 | - ccu_data_dict.get(DataTag.CCU_WAIT_SIGNAL, []), | ||
| 159 | - ccu_data_dict.get(DataTag.CCU_CHANNEL, []) | ||
| 160 | - )) | ||
| 161 | if not result: | 167 | if not result: |
| 162 | return [] | 168 | return [] |
| 163 | return self.get_timeline_header() + TraceViewManager.time_graph_trace( | 169 | return self.get_timeline_header() + TraceViewManager.time_graph_trace( |
| 164 | - TraceViewHeaderConstant.TOP_DOWN_TIME_GRAPH_HEAD, result) | 170 | + TraceViewHeaderConstant.TOP_DOWN_TIME_GRAPH_HEAD, result |
| 171 | + ) | ||
| 165 | 172 | ||
| 166 | def get_summary_data(self: any) -> tuple: | 173 | def get_summary_data(self: any) -> tuple: |
| 167 | """ | 174 | """ |
| @@ -189,35 +196,20 @@ class CCUMissionViewer(BaseViewer, ABC): | |||
| 189 | start_time = InfoConfReader().trans_syscnt_into_local_time(data.start_time) | 196 | start_time = InfoConfReader().trans_syscnt_into_local_time(data.start_time) |
| 190 | duration = InfoConfReader().duration_from_syscnt(data.end_time - data.start_time) | 197 | duration = InfoConfReader().duration_from_syscnt(data.end_time - data.start_time) |
| 191 | host_data = grouped_group_data.get(key, []) | 198 | host_data = grouped_group_data.get(key, []) |
| 192 | - args = { | 199 | + args = {"Physic Stream Id": data.stream_id, "Task Id": data.task_id, "Instruction ID": data.lp_instr_id} |
| 193 | - "Physic Stream Id": data.stream_id, | ||
| 194 | - "Task Id": data.task_id, | ||
| 195 | - "Instruction ID": data.lp_instr_id | ||
| 196 | - } | ||
| 197 | if host_data: | 200 | if host_data: |
| 198 | - args.update({ | 201 | + args.update({"Die Id": host_data[0].die_id, "Data Size": host_data[0].data_size}) |
| 199 | - "Die Id": host_data[0].die_id, | ||
| 200 | - "Data Size": host_data[0].data_size | ||
| 201 | - }) | ||
| 202 | if duration != 0: | 202 | if duration != 0: |
| 203 | - args.update({ | 203 | + args.update({"Bandwidth (MB/s)": host_data[0].data_size / duration * Constant.BYTE_US_TO_MB_S}) |
| 204 | - "Bandwidth (MB/s)": host_data[0].data_size / duration * Constant.BYTE_US_TO_MB_S | ||
| 205 | - }) | ||
| 206 | if host_data[0].reduce_op_type != CCUMissionViewer.RESERVED: | 204 | if host_data[0].reduce_op_type != CCUMissionViewer.RESERVED: |
| 207 | - args.update({ | 205 | + args.update( |
| 208 | - "Reduce Op Type": host_data[0].reduce_op_type, | 206 | + { |
| 209 | - "Input Data Type": host_data[0].input_data_type, | 207 | + "Reduce Op Type": host_data[0].reduce_op_type, |
| 210 | - "Output Data Type": host_data[0].output_data_type | 208 | + "Input Data Type": host_data[0].input_data_type, |
| 211 | - }) | 209 | + "Output Data Type": host_data[0].output_data_type, |
| 212 | - result.append( | 210 | + } |
| 213 | - [ | 211 | + ) |
| 214 | - data.time_type, | 212 | + result.append([data.time_type, self.pid, self.tid, start_time, duration if duration > 0 else 0, args]) |
| 215 | - self.pid, self.tid, | ||
| 216 | - start_time, | ||
| 217 | - duration if duration > 0 else 0, | ||
| 218 | - args | ||
| 219 | - ] | ||
| 220 | - ) | ||
| 221 | return result | 213 | return result |
| 222 | 214 | ||
| 223 | def get_formatted_wait_data(self, wait_data, wait_signal_data, channel_data): | 215 | def get_formatted_wait_data(self, wait_data, wait_signal_data, channel_data): |
| @@ -234,6 +226,12 @@ class CCUMissionViewer(BaseViewer, ABC): | |||
| 234 | key = (item.task_id, item.instr_id) | 226 | key = (item.task_id, item.instr_id) |
| 235 | grouped_wait_signal_data[key].append(item) | 227 | grouped_wait_signal_data[key].append(item) |
| 236 | 228 | ||
| 229 | + # channel data is classified by channel_id for easy access when calculating max delay channel and channel delay | ||
| 230 | + channel_data_classified = defaultdict(list) | ||
| 231 | + for item in channel_data: | ||
| 232 | + key = item.channel_id | ||
| 233 | + channel_data_classified[key].append(item) | ||
| 234 | + | ||
| 237 | for key, data_list in grouped_loop_data.items(): | 235 | for key, data_list in grouped_loop_data.items(): |
| 238 | latest_data = max(data_list, key=lambda x: x.end_time) | 236 | latest_data = max(data_list, key=lambda x: x.end_time) |
| 239 | start_time = InfoConfReader().trans_syscnt_into_local_time(latest_data.start_time) | 237 | start_time = InfoConfReader().trans_syscnt_into_local_time(latest_data.start_time) |
| @@ -243,31 +241,19 @@ class CCUMissionViewer(BaseViewer, ABC): | |||
| 243 | "Physic Stream Id": latest_data.stream_id, | 241 | "Physic Stream Id": latest_data.stream_id, |
| 244 | "Task Id": latest_data.task_id, | 242 | "Task Id": latest_data.task_id, |
| 245 | "Notify Instruction ID": latest_data.setckebit_instr_id, | 243 | "Notify Instruction ID": latest_data.setckebit_instr_id, |
| 246 | - "Notify Rank ID": latest_data.rel_id | 244 | + "Notify Rank ID": latest_data.rel_id, |
| 247 | } | 245 | } |
| 248 | if host_data: | 246 | if host_data: |
| 249 | - args.update({ | 247 | + args.update({"Die Id": host_data[0].die_id, "Mask": host_data[0].mask}) |
| 250 | - "Die Id": host_data[0].die_id, | ||
| 251 | - "Mask": host_data[0].mask | ||
| 252 | - }) | ||
| 253 | max_delay_channel, max_channel_delay = self.get_max_delay_channel_and_channel_delay( | 248 | max_delay_channel, max_channel_delay = self.get_max_delay_channel_and_channel_delay( |
| 254 | - host_data, | 249 | + host_data, latest_data, channel_data_classified |
| 255 | - latest_data, | ||
| 256 | - channel_data | ||
| 257 | ) | 250 | ) |
| 258 | if max_delay_channel and max_channel_delay: | 251 | if max_delay_channel and max_channel_delay: |
| 259 | - args.update({ | 252 | + args.update( |
| 260 | - "Maximum Delay Channel": max_delay_channel, | 253 | + {"Maximum Delay Channel": max_delay_channel, "Maximum Channel Delay": max_channel_delay} |
| 261 | - "Maximum Channel Delay": max_channel_delay | 254 | + ) |
| 262 | - }) | ||
| 263 | 255 | ||
| 264 | result.append( | 256 | result.append( |
| 265 | - [ | 257 | + [latest_data.time_type, self.pid, self.tid, start_time, duration if duration > 0 else 0, args] |
| 266 | - latest_data.time_type, | ||
| 267 | - self.pid, self.tid, | ||
| 268 | - start_time, | ||
| 269 | - duration if duration > 0 else 0, | ||
| 270 | - args | ||
| 271 | - ] | ||
| 272 | ) | 258 | ) |
| 273 | return result | 259 | return result |
| @@ -68,6 +68,7 @@ MissionSeedData BuildMissionData() | |||
| 68 | ChannelSeedData BuildChannelData() | 68 | ChannelSeedData BuildChannelData() |
| 69 | { | 69 | { |
| 70 | return { | 70 | return { |
| 71 | + {4, 9000, 0, 0, 999}, | ||
[Review] UT用例场景覆盖不足,没有断言验证前缀最大值行为(即 endTime 落在同一 channel 两条记录之间时返回历史最大值) 建议: 针对 GetMaxDelayChannel/BuildChannelIndex 补充用例,覆盖:
![]() ![]() | |||
| 71 | {3, 3000, 0, 0, 88}, | 72 | {3, 3000, 0, 0, 88}, |
| 72 | {4, 7500, 0, 0, 120}, | 73 | {4, 7500, 0, 0, 120}, |
| 73 | {5, 9000, 0, 0, 999} | 74 | {5, 9000, 0, 0, 999} |
| @@ -239,6 +240,65 @@ TEST_F(CcuMissionProcessorUTest, TestRunShouldReturnTrueWhenProcessorRunSuccess) | |||
| 239 | EXPECT_EQ(120u, waitData->maxChannelDelay); | 240 | EXPECT_EQ(120u, waitData->maxChannelDelay); |
| 240 | } | 241 | } |
| 241 | 242 | ||
| 243 | +TEST_F(CcuMissionProcessorUTest, TestRunShouldUsePrefixMaxDelayForChannelZero) | ||
| 244 | +{ | ||
| 245 | + std::shared_ptr<DBRunner> ccuDbRunner; | ||
| 246 | + MAKE_SHARED_RETURN_VOID(ccuDbRunner, DBRunner, CCU_DB_PATH); | ||
| 247 | + ChannelSeedData channelData { | ||
| 248 | + {0, 7900, 0, 0, 77}, | ||
| 249 | + {4, 7500, 0, 0, 120}, | ||
| 250 | + {0, 7000, 0, 0, 150}, | ||
| 251 | + {0, 8000, 0, 0, 999}, | ||
| 252 | + {0, 9000, 0, 0, 1000} | ||
| 253 | + }; | ||
| 254 | + EXPECT_TRUE(ccuDbRunner->DeleteData("DELETE FROM " + CHANNEL_TABLE)); | ||
| 255 | + EXPECT_TRUE(ccuDbRunner->InsertData(CHANNEL_TABLE, channelData)); | ||
| 256 | + | ||
| 257 | + std::shared_ptr<DBRunner> addInfoDbRunner; | ||
| 258 | + MAKE_SHARED_RETURN_VOID(addInfoDbRunner, DBRunner, CCU_ADD_INFO_DB_PATH); | ||
| 259 | + WaitSeedData waitData { | ||
| 260 | + {1, 10, 200, 0, 255, 0}, | ||
| 261 | + {1, 10, 200, 0, 255, 0}, | ||
| 262 | + {1, 10, 200, 0, 255, 4} | ||
| 263 | + }; | ||
| 264 | + EXPECT_TRUE(addInfoDbRunner->DeleteData("DELETE FROM " + WAIT_TABLE)); | ||
| 265 | + EXPECT_TRUE(addInfoDbRunner->InsertData(WAIT_TABLE, waitData)); | ||
| 266 | + | ||
| 267 | + DataInventory dataInventory; | ||
| 268 | + CCUMissionProcessor processor(PROF_PATH); | ||
| 269 | + EXPECT_TRUE(processor.Run(dataInventory, PROCESSOR_NAME_CCU_MISSION)); | ||
| 270 | + | ||
| 271 | + auto ccuData = dataInventory.GetPtr<std::vector<CCUMissionTimelineData>>(); | ||
| 272 | + ASSERT_NE(nullptr, ccuData); | ||
| 273 | + const auto *waitTimelineData = FindByType(*ccuData, CCU_TIME_TYPE_WAIT); | ||
| 274 | + ASSERT_NE(nullptr, waitTimelineData); | ||
| 275 | + EXPECT_TRUE(waitTimelineData->hasDelayChannel); | ||
| 276 | + EXPECT_EQ(0, waitTimelineData->maxDelayChannel); | ||
| 277 | + EXPECT_EQ(150u, waitTimelineData->maxChannelDelay); | ||
| 278 | +} | ||
| 279 | + | ||
| 280 | +TEST_F(CcuMissionProcessorUTest, TestRunShouldIgnoreChannelSamplesAtOrAfterMissionEnd) | ||
| 281 | +{ | ||
| 282 | + std::shared_ptr<DBRunner> dbRunner; | ||
| 283 | + MAKE_SHARED_RETURN_VOID(dbRunner, DBRunner, CCU_DB_PATH); | ||
| 284 | + ChannelSeedData channelData { | ||
| 285 | + {3, 8000, 0, 0, 88}, | ||
| 286 | + {4, 9000, 0, 0, 120} | ||
| 287 | + }; | ||
| 288 | + EXPECT_TRUE(dbRunner->DeleteData("DELETE FROM " + CHANNEL_TABLE)); | ||
| 289 | + EXPECT_TRUE(dbRunner->InsertData(CHANNEL_TABLE, channelData)); | ||
| 290 | + | ||
| 291 | + DataInventory dataInventory; | ||
| 292 | + CCUMissionProcessor processor(PROF_PATH); | ||
| 293 | + EXPECT_TRUE(processor.Run(dataInventory, PROCESSOR_NAME_CCU_MISSION)); | ||
| 294 | + | ||
| 295 | + auto ccuData = dataInventory.GetPtr<std::vector<CCUMissionTimelineData>>(); | ||
| 296 | + ASSERT_NE(nullptr, ccuData); | ||
| 297 | + const auto *waitData = FindByType(*ccuData, CCU_TIME_TYPE_WAIT); | ||
| 298 | + ASSERT_NE(nullptr, waitData); | ||
| 299 | + EXPECT_FALSE(waitData->hasDelayChannel); | ||
| 300 | +} | ||
| 301 | + | ||
| 242 | TEST_F(CcuMissionProcessorUTest, TestRunShouldReturnFalseWhenGetSyscntConversionParamsFailed) | 302 | TEST_F(CcuMissionProcessorUTest, TestRunShouldReturnFalseWhenGetSyscntConversionParamsFailed) |
| 243 | { | 303 | { |
| 244 | DataInventory dataInventory; | 304 | DataInventory dataInventory; |


【review】【编码】调用位置对--recordIter的取值,是对 lower_bound 返回的前一个元素取值,但 lower_bound 返回的是 >= missionData.endTime 的第一个元素,因此 --recordIter 是 timestamp < endTime 的最大元素。但当 channelRecords 中有多个相同 timestamp 的记录,lower_bound 可能返回第一个 >= endTime 的元素,而 prefix 中该位置的 maxDelay 可能不是该 timestamp 下所有记录合并后的最大值。建议在 BuildChannelIndex 中处理相同 timestamp 的情况。