已合并
[performance]enhanced the process of ccu max delay to lowered the time complexity from o(mn) to o(m+n) #343
xieanran创建于 7月7日
[performance]enhanced the process of ccu max delay to lowered the time complexity from o(mn) to o(m+n) #343
已合并
xieanran创建于 7月7日
3 个文件变更+193-89
Manalysis/csrc/domain/data_process/ai_task/ccu_mission_processor.cpp+71-13
@@ -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+ 
84std::string MakeCCUKey(uint64_t streamId, uint64_t taskId, uint64_t instrId)93std::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 
89CCUDelayChannel GetMaxDelayChannel(const std::vector<CCUWaitSignalInfo> &hostData, const CCUMissionInfo &missionData,98CCUDelayChannel 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

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

likedislike
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());
WangJie
WangJieWangJie7月8日

[Review] groupedChannelInfo 实际按唯一的 channelId 数分组,当 channelInfo 中重复的 channelId 较多时,以 channelId 的 size 预留 groupedChannelInfo 的 size 可能导致内存空间占用过多,建议确认实际是否有这种场景

likedislike
xieanran
xieanran
7月9日 评论:
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
Ppanzhaohu7月8日

[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 prefixRecords; if (!Reserve(prefixRecords, records.size())) { ERROR("Reserve ccu channel index failed."); continue; } // ... 填充 prefixRecords ... channelIndex[item.first] = std::move(prefixRecords); 这样只有在成功构建 prefix 数据后才写入索引,避免残留空条目。

likedislike
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+ 
123void ConvertMissionData(const OriCCUMissionData &oriData, std::vector<CCUMissionInfo> &missionData)180void 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;
Manalysis/viewer/ccu/ccu_mission_viewer.py+62-76
@@ -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_data78+ ),
79+ )
80+ for data in summary_data
73 ]81 ]
74 82 
75 @staticmethod83 @staticmethod
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, None86 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 = [channel89+ within_channel = []
82- for channel in within_channel90+ for host_channel_id in host_channel_ids:
83- if channel.timestamp < mission_data.end_time91+ 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 来计算最大延迟通道

likedislike
87 return max_delay_channel.channel_id, max_delay_channel.avg_bw99 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_type208+ "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 result213 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_id244+ "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_delay254+ )
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 result259 return result
Mtest/msprof_cpp/analysis_ut/domain/data_process/test/ccu_mission_processor_utest.cpp+60-0
@@ -68,6 +68,7 @@ MissionSeedData BuildMissionData()
68ChannelSeedData BuildChannelData()68ChannelSeedData BuildChannelData()
69{69{
70 return {70 return {
71+ {4, 9000, 0, 0, 999},
WangJie
WangJieWangJie7月8日

[Review] UT用例场景覆盖不足,没有断言验证前缀最大值行为(即 endTime 落在同一 channel 两条记录之间时返回历史最大值) 建议: 针对 GetMaxDelayChannel/BuildChannelIndex 补充用例,覆盖:

  1. endTime 落在同一 channel 两条记录之间
  2. 延迟先降后升的场景
  3. 无满足条件的记录
likedislike
xieanran
xieanran
7月9日 评论:
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+ 
242TEST_F(CcuMissionProcessorUTest, TestRunShouldReturnFalseWhenGetSyscntConversionParamsFailed)302TEST_F(CcuMissionProcessorUTest, TestRunShouldReturnFalseWhenGetSyscntConversionParamsFailed)
243{303{
244 DataInventory dataInventory;304 DataInventory dataInventory;