1# 2# Copyright (C) 2024 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import unittest 18import builtins 19from unittest import mock 20from config_builder import build_default_config, build_custom_config 21from command import ProfilerCommand 22from torq import DEFAULT_DUR_MS 23 24TEST_FAILURE_MSG = "Test failure." 25TEST_DUR_MS = 9000 26INVALID_DUR_MS = "invalid-dur-ms" 27ANDROID_SDK_VERSION_T = 33 28ANDROID_SDK_VERSION_S_V2 = 32 29 30COMMON_DEFAULT_CONFIG_BEGINNING_STRING_1 = f'''\ 31<<EOF 32 33buffers: {{ 34 size_kb: 4096 35 fill_policy: RING_BUFFER 36}} 37buffers {{ 38 size_kb: 4096 39 fill_policy: RING_BUFFER 40}} 41buffers: {{ 42 size_kb: 260096 43 fill_policy: RING_BUFFER 44}} 45 46data_sources: {{ 47 config {{ 48 name: "linux.process_stats" 49 process_stats_config {{ 50 scan_all_processes_on_start: true 51 }} 52 }} 53}} 54 55data_sources: {{ 56 config {{ 57 name: "android.log" 58 android_log_config {{ 59 }} 60 }} 61}} 62 63data_sources {{ 64 config {{ 65 name: "android.packages_list" 66 }} 67}} 68 69data_sources: {{ 70 config {{ 71 name: "linux.sys_stats" 72 target_buffer: 1 73 sys_stats_config {{ 74 stat_period_ms: 500 75 stat_counters: STAT_CPU_TIMES 76 stat_counters: STAT_FORK_COUNT 77 meminfo_period_ms: 1000 78 meminfo_counters: MEMINFO_ACTIVE_ANON 79 meminfo_counters: MEMINFO_ACTIVE_FILE 80 meminfo_counters: MEMINFO_INACTIVE_ANON 81 meminfo_counters: MEMINFO_INACTIVE_FILE 82 meminfo_counters: MEMINFO_KERNEL_STACK 83 meminfo_counters: MEMINFO_MLOCKED 84 meminfo_counters: MEMINFO_SHMEM 85 meminfo_counters: MEMINFO_SLAB 86 meminfo_counters: MEMINFO_SLAB_UNRECLAIMABLE 87 meminfo_counters: MEMINFO_VMALLOC_USED 88 meminfo_counters: MEMINFO_MEM_FREE 89 meminfo_counters: MEMINFO_SWAP_FREE 90 vmstat_period_ms: 1000 91 vmstat_counters: VMSTAT_PGFAULT 92 vmstat_counters: VMSTAT_PGMAJFAULT 93 vmstat_counters: VMSTAT_PGFREE 94 vmstat_counters: VMSTAT_PGPGIN 95 vmstat_counters: VMSTAT_PGPGOUT 96 vmstat_counters: VMSTAT_PSWPIN 97 vmstat_counters: VMSTAT_PSWPOUT 98 vmstat_counters: VMSTAT_PGSCAN_DIRECT 99 vmstat_counters: VMSTAT_PGSTEAL_DIRECT 100 vmstat_counters: VMSTAT_PGSCAN_KSWAPD 101 vmstat_counters: VMSTAT_PGSTEAL_KSWAPD 102 vmstat_counters: VMSTAT_WORKINGSET_REFAULT''' 103 104CPUFREQ_STRING_NEW_ANDROID = f' cpufreq_period_ms: 500' 105 106COMMON_DEFAULT_CONFIG_BEGINNING_STRING_2 = f'''\ 107 }} 108 }} 109}} 110 111data_sources: {{ 112 config {{ 113 name: "android.surfaceflinger.frametimeline" 114 target_buffer: 2 115 }} 116}} 117 118data_sources: {{ 119 config {{ 120 name: "linux.ftrace" 121 target_buffer: 2 122 ftrace_config {{''' 123 124COMMON_DEFAULT_CONFIG_MIDDLE_STRING = f'''\ 125 atrace_categories: "aidl" 126 atrace_categories: "am" 127 atrace_categories: "dalvik" 128 atrace_categories: "binder_lock" 129 atrace_categories: "binder_driver" 130 atrace_categories: "bionic" 131 atrace_categories: "camera" 132 atrace_categories: "disk" 133 atrace_categories: "freq" 134 atrace_categories: "idle" 135 atrace_categories: "gfx" 136 atrace_categories: "hal" 137 atrace_categories: "input" 138 atrace_categories: "pm" 139 atrace_categories: "power" 140 atrace_categories: "res" 141 atrace_categories: "rro" 142 atrace_categories: "sched" 143 atrace_categories: "sm" 144 atrace_categories: "ss" 145 atrace_categories: "thermal" 146 atrace_categories: "video" 147 atrace_categories: "view" 148 atrace_categories: "wm" 149 atrace_apps: "lmkd" 150 atrace_apps: "system_server" 151 atrace_apps: "com.android.systemui" 152 atrace_apps: "com.google.android.gms" 153 atrace_apps: "com.google.android.gms.persistent" 154 atrace_apps: "android:ui" 155 atrace_apps: "com.google.android.apps.maps" 156 atrace_apps: "*" 157 buffer_size_kb: 16384 158 drain_period_ms: 150 159 symbolize_ksyms: true 160 }} 161 }} 162}}''' 163 164COMMON_CONFIG_ENDING_STRING = f'''\ 165write_into_file: true 166file_write_period_ms: 5000 167max_file_size_bytes: 100000000000 168flush_period_ms: 5000 169incremental_state_config {{ 170 clear_period_ms: 5000 171}} 172 173''' 174 175COMMON_DEFAULT_FTRACE_EVENTS = f'''\ 176 ftrace_events: "dmabuf_heap/dma_heap_stat" 177 ftrace_events: "ftrace/print" 178 ftrace_events: "gpu_mem/gpu_mem_total" 179 ftrace_events: "ion/ion_stat" 180 ftrace_events: "kmem/ion_heap_grow" 181 ftrace_events: "kmem/ion_heap_shrink" 182 ftrace_events: "kmem/rss_stat" 183 ftrace_events: "lowmemorykiller/lowmemory_kill" 184 ftrace_events: "mm_event/mm_event_record" 185 ftrace_events: "oom/mark_victim" 186 ftrace_events: "oom/oom_score_adj_update" 187 ftrace_events: "power/cpu_frequency" 188 ftrace_events: "power/cpu_idle" 189 ftrace_events: "power/gpu_frequency" 190 ftrace_events: "power/suspend_resume" 191 ftrace_events: "power/wakeup_source_activate" 192 ftrace_events: "power/wakeup_source_deactivate" 193 ftrace_events: "sched/sched_blocked_reason" 194 ftrace_events: "sched/sched_process_exit" 195 ftrace_events: "sched/sched_process_free" 196 ftrace_events: "sched/sched_switch" 197 ftrace_events: "sched/sched_wakeup" 198 ftrace_events: "sched/sched_wakeup_new" 199 ftrace_events: "sched/sched_waking" 200 ftrace_events: "task/task_newtask" 201 ftrace_events: "task/task_rename" 202 ftrace_events: "vmscan/*" 203 ftrace_events: "workqueue/*"''' 204 205DEFAULT_CONFIG_9000_DUR_MS = f'''\ 206{COMMON_DEFAULT_CONFIG_BEGINNING_STRING_1} 207{CPUFREQ_STRING_NEW_ANDROID} 208{COMMON_DEFAULT_CONFIG_BEGINNING_STRING_2} 209{COMMON_DEFAULT_FTRACE_EVENTS} 210{COMMON_DEFAULT_CONFIG_MIDDLE_STRING} 211duration_ms: {TEST_DUR_MS} 212{COMMON_CONFIG_ENDING_STRING}EOF''' 213 214DEFAULT_CONFIG_EXCLUDED_FTRACE_EVENTS = f'''\ 215{COMMON_DEFAULT_CONFIG_BEGINNING_STRING_1} 216{CPUFREQ_STRING_NEW_ANDROID} 217{COMMON_DEFAULT_CONFIG_BEGINNING_STRING_2} 218 ftrace_events: "dmabuf_heap/dma_heap_stat" 219 ftrace_events: "ftrace/print" 220 ftrace_events: "gpu_mem/gpu_mem_total" 221 ftrace_events: "ion/ion_stat" 222 ftrace_events: "kmem/ion_heap_grow" 223 ftrace_events: "kmem/ion_heap_shrink" 224 ftrace_events: "kmem/rss_stat" 225 ftrace_events: "lowmemorykiller/lowmemory_kill" 226 ftrace_events: "oom/mark_victim" 227 ftrace_events: "oom/oom_score_adj_update" 228 ftrace_events: "power/cpu_frequency" 229 ftrace_events: "power/cpu_idle" 230 ftrace_events: "power/gpu_frequency" 231 ftrace_events: "power/wakeup_source_activate" 232 ftrace_events: "power/wakeup_source_deactivate" 233 ftrace_events: "sched/sched_blocked_reason" 234 ftrace_events: "sched/sched_process_exit" 235 ftrace_events: "sched/sched_process_free" 236 ftrace_events: "sched/sched_switch" 237 ftrace_events: "sched/sched_wakeup" 238 ftrace_events: "sched/sched_wakeup_new" 239 ftrace_events: "sched/sched_waking" 240 ftrace_events: "task/task_newtask" 241 ftrace_events: "task/task_rename" 242 ftrace_events: "vmscan/*" 243 ftrace_events: "workqueue/*" 244{COMMON_DEFAULT_CONFIG_MIDDLE_STRING} 245duration_ms: {DEFAULT_DUR_MS} 246{COMMON_CONFIG_ENDING_STRING}EOF''' 247 248DEFAULT_CONFIG_INCLUDED_FTRACE_EVENTS = f'''\ 249{COMMON_DEFAULT_CONFIG_BEGINNING_STRING_1} 250{CPUFREQ_STRING_NEW_ANDROID} 251{COMMON_DEFAULT_CONFIG_BEGINNING_STRING_2} 252 ftrace_events: "dmabuf_heap/dma_heap_stat" 253 ftrace_events: "ftrace/print" 254 ftrace_events: "gpu_mem/gpu_mem_total" 255 ftrace_events: "ion/ion_stat" 256 ftrace_events: "kmem/ion_heap_grow" 257 ftrace_events: "kmem/ion_heap_shrink" 258 ftrace_events: "kmem/rss_stat" 259 ftrace_events: "lowmemorykiller/lowmemory_kill" 260 ftrace_events: "mm_event/mm_event_record" 261 ftrace_events: "oom/mark_victim" 262 ftrace_events: "oom/oom_score_adj_update" 263 ftrace_events: "power/cpu_frequency" 264 ftrace_events: "power/cpu_idle" 265 ftrace_events: "power/gpu_frequency" 266 ftrace_events: "power/suspend_resume" 267 ftrace_events: "power/wakeup_source_activate" 268 ftrace_events: "power/wakeup_source_deactivate" 269 ftrace_events: "sched/sched_blocked_reason" 270 ftrace_events: "sched/sched_process_exit" 271 ftrace_events: "sched/sched_process_free" 272 ftrace_events: "sched/sched_switch" 273 ftrace_events: "sched/sched_wakeup" 274 ftrace_events: "sched/sched_wakeup_new" 275 ftrace_events: "sched/sched_waking" 276 ftrace_events: "task/task_newtask" 277 ftrace_events: "task/task_rename" 278 ftrace_events: "vmscan/*" 279 ftrace_events: "workqueue/*" 280 ftrace_events: "mock_ftrace_event1" 281 ftrace_events: "mock_ftrace_event2" 282{COMMON_DEFAULT_CONFIG_MIDDLE_STRING} 283duration_ms: {DEFAULT_DUR_MS} 284{COMMON_CONFIG_ENDING_STRING}EOF''' 285 286DEFAULT_CONFIG_OLD_ANDROID = f'''\ 287{COMMON_DEFAULT_CONFIG_BEGINNING_STRING_1} 288 289{COMMON_DEFAULT_CONFIG_BEGINNING_STRING_2} 290{COMMON_DEFAULT_FTRACE_EVENTS} 291{COMMON_DEFAULT_CONFIG_MIDDLE_STRING} 292duration_ms: {DEFAULT_DUR_MS} 293{COMMON_CONFIG_ENDING_STRING}EOF''' 294 295COMMON_CUSTOM_CONFIG_BEGINNING_STRING = f'''\ 296 297buffers: {{ 298 size_kb: 4096 299 fill_policy: RING_BUFFER 300}} 301 302data_sources: {{ 303 config {{ 304 name: "linux.ftrace" 305 target_buffer: 2 306 ftrace_config {{ 307 ftrace_events: "dmabuf_heap/dma_heap_stat" 308 atrace_categories: "aidl" 309 atrace_apps: "lmkd" 310 buffer_size_kb: 16384 311 drain_period_ms: 150 312 symbolize_ksyms: true 313 }} 314 }} 315}}''' 316 317CUSTOM_CONFIG_9000_DUR_MS = f'''\ 318{COMMON_CUSTOM_CONFIG_BEGINNING_STRING} 319duration_ms: {TEST_DUR_MS} 320{COMMON_CONFIG_ENDING_STRING}''' 321 322CUSTOM_CONFIG_9000_DUR_MS_WITH_WHITE_SPACE = f'''\ 323{COMMON_CUSTOM_CONFIG_BEGINNING_STRING} 324duration_ms: {TEST_DUR_MS} 325{COMMON_CONFIG_ENDING_STRING}''' 326 327CUSTOM_CONFIG_INVALID_DUR_MS = f'''\ 328{COMMON_CUSTOM_CONFIG_BEGINNING_STRING} 329duration_ms: {INVALID_DUR_MS} 330{COMMON_CONFIG_ENDING_STRING}''' 331 332CUSTOM_CONFIG_NO_DUR_MS = f'''\ 333{COMMON_CUSTOM_CONFIG_BEGINNING_STRING} 334{COMMON_CONFIG_ENDING_STRING}''' 335 336 337class ConfigBuilderUnitTest(unittest.TestCase): 338 339 def setUp(self): 340 self.command = ProfilerCommand( 341 None, "custom", None, None, DEFAULT_DUR_MS, None, None, "test-path", 342 None, None, None, None, None, None, None) 343 344 def test_build_default_config_setting_valid_dur_ms(self): 345 self.command.dur_ms = TEST_DUR_MS 346 347 config, error = build_default_config(self.command, ANDROID_SDK_VERSION_T) 348 349 self.assertEqual(error, None) 350 self.assertEqual(config, DEFAULT_CONFIG_9000_DUR_MS) 351 352 def test_build_default_config_on_old_android_version(self): 353 config, error = build_default_config(self.command, ANDROID_SDK_VERSION_S_V2) 354 355 self.assertEqual(error, None) 356 self.assertEqual(config, DEFAULT_CONFIG_OLD_ANDROID) 357 358 def test_build_default_config_setting_invalid_dur_ms(self): 359 self.command.dur_ms = None 360 361 with self.assertRaises(ValueError) as e: 362 build_default_config(self.command, ANDROID_SDK_VERSION_T) 363 364 self.assertEqual(str(e.exception), ("Cannot create config because a valid" 365 " dur_ms was not set.")) 366 367 def test_build_default_config_removing_valid_excluded_ftrace_events(self): 368 self.command.excluded_ftrace_events = ["power/suspend_resume", 369 "mm_event/mm_event_record"] 370 371 config, error = build_default_config(self.command, ANDROID_SDK_VERSION_T) 372 373 self.assertEqual(error, None) 374 self.assertEqual(config, DEFAULT_CONFIG_EXCLUDED_FTRACE_EVENTS) 375 376 def test_build_default_config_removing_invalid_excluded_ftrace_events(self): 377 self.command.excluded_ftrace_events = ["invalid_ftrace_event"] 378 379 config, error = build_default_config(self.command, ANDROID_SDK_VERSION_T) 380 381 self.assertEqual(config, None) 382 self.assertNotEqual(error, None) 383 self.assertEqual(error.message, ("Cannot remove ftrace event %s from config" 384 " because it is not one of the config's" 385 " ftrace events." % 386 self.command.excluded_ftrace_events[0] 387 )) 388 self.assertEqual(error.suggestion, ("Please specify one of the following" 389 " possible ftrace events:\n\t" 390 " dmabuf_heap/dma_heap_stat\n\t" 391 " ftrace/print\n\t" 392 " gpu_mem/gpu_mem_total\n\t" 393 " ion/ion_stat\n\t" 394 " kmem/ion_heap_grow\n\t" 395 " kmem/ion_heap_shrink\n\t" 396 " kmem/rss_stat\n\t" 397 " lowmemorykiller/lowmemory_kill\n\t" 398 " mm_event/mm_event_record\n\t" 399 " oom/mark_victim\n\t" 400 " oom/oom_score_adj_update\n\t" 401 " power/cpu_frequency\n\t" 402 " power/cpu_idle\n\t" 403 " power/gpu_frequency\n\t" 404 " power/suspend_resume\n\t" 405 " power/wakeup_source_activate\n\t" 406 " power/wakeup_source_deactivate\n\t" 407 " sched/sched_blocked_reason\n\t" 408 " sched/sched_process_exit\n\t" 409 " sched/sched_process_free\n\t" 410 " sched/sched_switch\n\t" 411 " sched/sched_wakeup\n\t" 412 " sched/sched_wakeup_new\n\t" 413 " sched/sched_waking\n\t" 414 " task/task_newtask\n\t" 415 " task/task_rename\n\t" 416 " vmscan/*\n\t" 417 " workqueue/*")) 418 419 def test_build_default_config_adding_valid_included_ftrace_events(self): 420 self.command.included_ftrace_events = ["mock_ftrace_event1", 421 "mock_ftrace_event2"] 422 423 config, error = build_default_config(self.command, ANDROID_SDK_VERSION_T) 424 425 self.assertEqual(error, None) 426 self.assertEqual(config, DEFAULT_CONFIG_INCLUDED_FTRACE_EVENTS) 427 428 def test_build_default_config_adding_invalid_included_ftrace_events(self): 429 self.command.included_ftrace_events = ["power/suspend_resume"] 430 431 config, error = build_default_config(self.command, ANDROID_SDK_VERSION_T) 432 433 self.assertEqual(config, None) 434 self.assertNotEqual(error, None) 435 self.assertEqual(error.message, ("Cannot add ftrace event %s to config" 436 " because it is already one of the" 437 " config's ftrace events." % 438 self.command.included_ftrace_events[0] 439 )) 440 self.assertEqual(error.suggestion, ("Please do not specify any of the" 441 " following ftrace events that are" 442 " already included:\n\t" 443 " dmabuf_heap/dma_heap_stat\n\t" 444 " ftrace/print\n\t" 445 " gpu_mem/gpu_mem_total\n\t" 446 " ion/ion_stat\n\t" 447 " kmem/ion_heap_grow\n\t" 448 " kmem/ion_heap_shrink\n\t" 449 " kmem/rss_stat\n\t" 450 " lowmemorykiller/lowmemory_kill\n\t" 451 " mm_event/mm_event_record\n\t" 452 " oom/mark_victim\n\t" 453 " oom/oom_score_adj_update\n\t" 454 " power/cpu_frequency\n\t" 455 " power/cpu_idle\n\t" 456 " power/gpu_frequency\n\t" 457 " power/suspend_resume\n\t" 458 " power/wakeup_source_activate\n\t" 459 " power/wakeup_source_deactivate\n\t" 460 " sched/sched_blocked_reason\n\t" 461 " sched/sched_process_exit\n\t" 462 " sched/sched_process_free\n\t" 463 " sched/sched_switch\n\t" 464 " sched/sched_wakeup\n\t" 465 " sched/sched_wakeup_new\n\t" 466 " sched/sched_waking\n\t" 467 " task/task_newtask\n\t" 468 " task/task_rename\n\t" 469 " vmscan/*\n\t" 470 " workqueue/*")) 471 472 @mock.patch("builtins.open", mock.mock_open( 473 read_data=CUSTOM_CONFIG_9000_DUR_MS)) 474 def test_build_custom_config_extracting_valid_dur_ms(self): 475 config, error = build_custom_config(self.command) 476 477 self.assertEqual(error, None) 478 self.assertEqual(config, f"<<EOF\n\n{CUSTOM_CONFIG_9000_DUR_MS}\n\n\nEOF") 479 self.assertEqual(self.command.dur_ms, TEST_DUR_MS) 480 481 @mock.patch("builtins.open", mock.mock_open( 482 read_data=CUSTOM_CONFIG_9000_DUR_MS_WITH_WHITE_SPACE)) 483 def test_build_custom_config_extracting_valid_dur_ms_with_white_space(self): 484 config, error = build_custom_config(self.command) 485 486 self.assertEqual(error, None) 487 self.assertEqual(config, ( 488 f"<<EOF\n\n{CUSTOM_CONFIG_9000_DUR_MS_WITH_WHITE_SPACE}\n\n\nEOF")) 489 self.assertEqual(self.command.dur_ms, TEST_DUR_MS) 490 491 @mock.patch("builtins.open", mock.mock_open( 492 read_data=CUSTOM_CONFIG_INVALID_DUR_MS)) 493 def test_build_custom_config_extracting_invalid_dur_ms_error(self): 494 config, error = build_custom_config(self.command) 495 496 self.assertNotEqual(error, None) 497 self.assertEqual(config, None) 498 self.assertEqual(error.message, 499 ("Failed to parse custom perfetto-config on local file" 500 " path: %s. Invalid duration_ms field in config." 501 % self.command.perfetto_config)) 502 self.assertEqual(error.suggestion, 503 ("Make sure the perfetto config passed via arguments has a" 504 " valid duration_ms value.")) 505 506 @mock.patch("builtins.open", mock.mock_open( 507 read_data=CUSTOM_CONFIG_NO_DUR_MS)) 508 def test_build_custom_config_injecting_dur_ms(self): 509 duration_string = "duration_ms: " + str(self.command.dur_ms) 510 511 config, error = build_custom_config(self.command) 512 513 self.assertEqual(error, None) 514 self.assertEqual(config, ( 515 f"<<EOF\n\n{CUSTOM_CONFIG_NO_DUR_MS}\n{duration_string}\n\nEOF")) 516 517 @mock.patch.object(builtins, "open") 518 def test_build_custom_config_parsing_error(self, mock_open_file): 519 self.command.dur_ms = None 520 mock_open_file.side_effect = Exception(TEST_FAILURE_MSG) 521 522 config, error = build_custom_config(self.command) 523 524 self.assertNotEqual(error, None) 525 self.assertEqual(config, None) 526 self.assertEqual(error.message, ("Failed to parse custom perfetto-config on" 527 " local file path: %s. %s" 528 % (self.command.perfetto_config, 529 TEST_FAILURE_MSG))) 530 self.assertEqual(error.suggestion, None) 531 532 533if __name__ == '__main__': 534 unittest.main() 535