Android 15 (API লেভেল 35) এবং তার পরবর্তী ভার্সনে, অ্যাপ-চালিত প্রোফাইলিং আপনার অ্যাপকে
ProfilingManager API বা AndroidX Core (androidx.core:core 1.15.0 বা
তার পরবর্তী ভার্সন) বিল্ডার ব্যবহার করে কোডে স্পষ্টভাবে সমর্থিত প্রোফাইল রেকর্ডিং শুরু এবং বন্ধ করতে দেয়। আপনি প্রোফাইল টাইমিং বা সেশন প্যারামিটার—যেমন সময়সীমা, বাফার নীতি ও স্যাম্পলিং রেট—নিয়ন্ত্রণ করতে চাইলে
অথবা নির্দিষ্ট ব্যবহারকারীর অভিজ্ঞতা বা রানটাইম পরিস্থিতি টার্গেট করতে চাইলে
সরাসরি প্রোফাইলিং ব্যবহার করুন।
ডিপেন্ডেন্সি যোগ করুন
ProfilingManager API-এর সাথে সেরা অভিজ্ঞতা পেতে, আপনার build.gradle.kts ফাইলে নিম্নলিখিত
Jetpack লাইব্রেরি যোগ করুন।
Kotlin
dependencies { implementation("androidx.tracing:tracing-ktx:2.0.5") implementation("androidx.core:core:1.19.1") }
গ্ৰুভি
dependencies { implementation 'androidx.tracing:tracing-ktx:2.0.5' implementation 'androidx.core:core:1.19.1' }
সিস্টেম ট্রেস রেকর্ড করুন
প্রয়োজনীয় ডিপেন্ডেন্সি যোগ করার পরে, সিস্টেম ট্রেস রেকর্ড করতে নিম্নলিখিত কোড ব্যবহার করুন। এই উদাহরণে দেখানো হয়েছে কীভাবে প্রোফাইলিং সেশন শুরু করতে হয় এবং একই সাথে মেন থ্রেড থেকে ভারী অপারেশন নিরাপদে ম্যানেজ করা যায়।
Kotlin
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @Composable fun ProfiledScreen(modifier: Modifier = Modifier) { // Use the application context: requestProfiling resolves the ProfilingManager // system service from it, so there's no reason to hand it a short-lived Activity. val appContext = LocalContext.current.applicationContext val scope = rememberCoroutineScope() Button( onClick = { // Run the orchestration off the main thread. Profiling a heavy operation // on the UI thread would freeze the UI (ANR) and distort the very metrics // you're trying to capture. // // Note: this scope is tied to composition. If the user leaves this screen // mid-session, the coroutine is cancelled and stopSignal.cancel() might not // run, but setDurationMs() acts as a safety net and ends the profile. scope.launch(Dispatchers.Default) { val callbackExecutor = Dispatchers.IO.asExecutor() val resultCallback = Consumer<ProfilingResult> { profilingResult -> if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) { Log.d("ProfileTest", "Result file: ${profilingResult.resultFilePath}") } else { // errorMessage explains the failure (e.g., rate limiting); keep it. Log.e( "ProfileTest", "Profiling failed errorCode=${profilingResult.errorCode} " + "errorMessage=${profilingResult.errorMessage}" ) } } val stopSignal = CancellationSignal() val requestBuilder = SystemTraceRequestBuilder().apply { setCancellationSignal(stopSignal) setTag("FOO") // Caller-supplied tag for identification. setDurationMs(60000) // Hard cap: ends the session if cancel() never fires. setBufferFillPolicy(BufferFillPolicy.RING_BUFFER) setBufferSizeKb(32768) } // 1. Start the session. This is asynchronous system IPC. The tracing // engine takes a moment to start and allocate buffers. requestProfiling(appContext, requestBuilder.build(), callbackExecutor, resultCallback) // 2. The API exposes no "profiling started" signal, so pad with a short, // best-effort delay before running the code you care about. This is // approximate. Increase it on slower or heavily loaded devices. delay(STARTUP_PADDING_MS) // 3. The session is already recording every thread in your app. This slice // doesn't scope what's captured. It just labels this region of the // timeline so heavyOperation() is easier to find. trace { } closes the // section even if the block throws. trace("MyApp:HeavyOperation") { heavyOperation() } // 4. Stop recording. Until this fires or the setDurationMs() cap is // reached (whichever comes first), the session keeps capturing app-wide // activity. stopSignal.cancel() } } ) { Text("Run & Profile Heavy Operation") } } // Best-effort wait for the system trace engine to initialize before profiling. // There is no deterministic start callback; tune this for your target devices. private const val STARTUP_PADDING_MS = 100L fun heavyOperation() { // Background computations to profile. }
জাভা
void sampleRecordSystemTrace(Context context) { // Use the application context: requestProfiling resolves the ProfilingManager // system service from it, so there's no reason to hand it a short-lived Activity. Context appContext = context.getApplicationContext(); // Run the orchestration off the main thread. Profiling a heavy operation // on the UI thread would freeze the UI (ANR) and distort the very metrics // you're trying to capture. profilingExecutor.execute(() -> { Executor callbackExecutor = Executors.newSingleThreadExecutor(); Consumer<ProfilingResult> resultCallback = profilingResult -> { if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) { Log.d("ProfileTest", "Result file: " + profilingResult.getResultFilePath()); } else { // errorMessage explains the failure (e.g., rate limiting); keep it. Log.e( "ProfileTest", "Profiling failed errorCode=" + profilingResult.getErrorCode() + " errorMessage=" + profilingResult.getErrorMessage()); } }; CancellationSignal stopSignal = new CancellationSignal(); SystemTraceRequestBuilder requestBuilder = new SystemTraceRequestBuilder(); requestBuilder.setCancellationSignal(stopSignal); requestBuilder.setTag("FOO"); // Caller-supplied tag for identification. requestBuilder.setDurationMs(60000); // Hard cap: ends the session if cancel() never fires. requestBuilder.setBufferFillPolicy(BufferFillPolicy.RING_BUFFER); requestBuilder.setBufferSizeKb(32768); // 1. Start the session. This is asynchronous system IPC. The tracing // engine takes a moment to start and allocate buffers. Profiling.requestProfiling( appContext, requestBuilder.build(), callbackExecutor, resultCallback); // 2. The API exposes no "profiling started" signal, so pad with a short, // best-effort delay before running the code you care about. This is // approximate. Increase it on slower or heavily loaded devices. try { Thread.sleep(STARTUP_PADDING_MS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); stopSignal.cancel(); return; } // 3. The session is already recording every thread in your app. This slice // doesn't scope what's captured. It just labels this region of the // timeline so heavyOperation() is easier to find. try/finally closes the // section even if heavyOperation() throws. Trace.beginSection("MyApp:HeavyOperation"); try { heavyOperation(); } finally { Trace.endSection(); } // 4. Stop recording. Until this fires or the setDurationMs() cap is // reached (whichever comes first), the session keeps capturing app-wide // activity. stopSignal.cancel(); }); } // Best-effort wait for the system trace engine to initialize before profiling. // There is no deterministic start callback; tune this for your target devices. private static final long STARTUP_PADDING_MS = 100L; // Single background thread that runs the profiling orchestration. private final ExecutorService profilingExecutor = Executors.newSingleThreadExecutor(); void heavyOperation() { // Background computations to profile. }
স্যাম্পেল কোড নিম্নলিখিত ধাপগুলি অনুসরণ করে প্রোফাইলিং সেশন সেট-আপ ও ম্যানেজ করে:
- এক্সিকিউটর সেট-আপ করা: মূল থ্রেড থেকে প্রোফাইলিং ফলাফল পেতে
নন-UI
Executorতৈরি করুন। - প্রোফাইলিং ফলাফল ম্যানেজ করা:
ProfilingManagerথেকে সম্পূর্ণ ফলাফল বা সমস্যা সংক্রান্ত তথ্য পেতে একটিConsumer<ProfilingResult>কলব্যাক তৈরি করুন। প্রোফাইলিং অনুরোধ তৈরি করুন: সেশন সেটিংস কাস্টমাইজ করতে (অথবা সিস্টেম ডিফল্ট ব্যবহার করার জন্য কাস্টমাইজেশন বাদ দিতে) একটি
SystemTraceRequestBuilderতৈরি করুন:setTag(): কলারের দেওয়া ট্যাগ অ্যাটাচ করে যাProfilingResult.getTag-এর মাধ্যমে ফেরত পাঠানো হয় এবং আউটপুট ফাইলের নামের শেষে যোগ করা হয় (প্রথম ২০টি ছোট হাতের আলফানিউমেরিক অক্ষর এবং হাইফেন পর্যন্ত)।setDurationMs(): মিলি সেকেন্ডে সর্বাধিক রেকর্ডিংয়ের সময়সীমা সেট করে (যেমন, ৬০ সেকেন্ডের জন্য60000)।setBufferFillPolicy(): বাফার কীভাবে ক্ষমতার সীমা ম্যানেজ করে তা নির্ধারণ করে:BufferFillPolicy.RING_BUFFERসম্পূর্ণ হলে সবচেয়ে পুরনো ডেটা ওভাররাইট করে, যেখানেBufferFillPolicy.DISCARDবাফার সম্পূর্ণ হয়ে গেলে নতুন ডেটা রেকর্ড করা বন্ধ করে দেয়।setBufferSizeKb(): কিলোবাইটে রেকর্ডিং বাফার সাইজ সেট করে।
ঐচ্ছিক: রেকর্ডিং আগে বন্ধ করুন: টার্গেট অপারেশন শেষ হওয়ার সাথে সাথে সেশন বন্ধ করতে
CancellationSignal(stopSignal) পাস করুন, এখনও পর্যন্ত রেকর্ড করা ডেটা সেভ করুন এবং আপনার কলব্যাকেProfilingResultডেলিভার করুন। বিস্তারিত জানতে অ্যাক্টিভ রেকর্ডিং বন্ধ করুন বিকল্প দেখুন।শুরু করুন এবং ফলাফল পান: ব্যাকগ্রাউন্ডে রেকর্ডিং শুরু করতে
requestProfiling()-এ কল করুন। সেশন শেষ হলে,resultCallback.accept()একটিProfilingResultপাবে:- সফল হলে (
ERROR_NONE), অন-ডিভাইস পাথ পেতেgetResultFilePath()-এ কল করুন।adb-এর মাধ্যমে ফাইল পুল করা বাWorkManager-এর মাধ্যমে আপলোড করার নির্দেশাবলীর জন্য, প্রোফাইল ফিরিয়ে আনা ও আপলোড করা দেখুন। - ব্যর্থ হলে,
getErrorCode()এবংgetErrorMessage()পরীক্ষা করুন। রেট লিমিটিং ছাড়াও অন্যান্য কারণে অনুরোধ ব্যর্থ হতে পারে। - ফলাফল ডেলিভার করার আগে আপনার অ্যাপ বন্ধ হয়ে গেলে, সিস্টেম
ফলাফলটি সারিতে রাখে এবং পরের বার আপনার অ্যাপ
registerForAllProfilingResults-এর সাথে গ্লোবাল লিসেনার রেজিস্টার করলে এটি ডেলিভার করে।
- সফল হলে (
কাস্টম ট্রেস পয়েন্ট যোগ করুন: টাইমলাইনের অঞ্চল লেবেল করতে
trace("...") { ... }(বাTrace.beginSection()/Trace.endSection()) দিয়ে অপারেশন র্যাপ করুন।
অ্যাক্টিভ রেকর্ডিং বন্ধ করা
setDurationMs()-এর মেয়াদ শেষ হওয়ার জন্য অপেক্ষা না করে, ইন্টার্যাকশন সম্পূর্ণ হওয়ার সাথে সাথেই ক্যাপচার করা বন্ধ করতে,
CancellationSignal-কে setCancellationSignal() বা requestProfiling()-এ পাস করুন। আপনি যদি CancellationSignal এবং setDurationMs(), দুটিই উল্লেখ করেন, তাহলে যেটি আগে ঘটবে সেটি সেশন
শেষ করবে; আপনি যদি কোনওটিই উল্লেখ না করেন, তাহলে সিস্টেম একটি ডিফল্ট সময়কাল প্রয়োগ করে।
cancel()-কে কল করলে রেকর্ডিং বাতিল হয়ে যায় না। সিস্টেম ক্যাপচার করা বন্ধ করে দেয়,
সেই সময় পর্যন্ত সংগ্রহ করা ডেটা রিড্যাক্ট ও সেভ করে এবং
ERROR_NONE সহ আপনার শ্রোতার কাছে ProfilingResult ডেলিভার করে।
কল স্ট্যাক স্যাম্পলিংয়ের মাধ্যমে সিস্টেম ট্রেস রেকর্ড করুন
Android 17 (API লেভেল ৩৭) ও তার পরবর্তী ভার্সন (অথবা আপডেট করা Profiling মেইনলাইন মডিউল সহ Android 16)
চালানো ডিভাইসে, ProfilingManager একটি সেশনে সিস্টেম ট্রেসের পাশাপাশি স্যাম্পেল করা কল স্ট্যাক
সংগ্রহ করার সুবিধা দেয়, যাতে আপনি টাইমলাইন ইভেন্ট, যেমন থ্রেড স্টেট, ফ্রেম ও কাস্টম ট্রেস স্লাইস,
স্পষ্ট ট্রেস ইন্সট্রুমেন্টেশন ছাড়াই মেথডের জন্য CPU কল স্ট্যাকের সাথে
একসাথে দেখতে পারেন।
এই উদাহরণে আরও দেখানো হয়েছে যে কীভাবে Jetpack বিল্ডার ছাড়াই সরাসরি ProfilingManager.requestProfiling
কল করা যায়। এর জন্য কাজ করে এমন প্রোফাইল
টাইপ (PROFILING_TYPE_SYSTEM_TRACE) উল্লেখ করতে হবে এবং কনফিগারেশন
Bundle পাস করতে হবে:
Kotlin
val profilingManager = context.getSystemService(ProfilingManager::class.java) // Pass a Bundle to customize the capture, or pass null for default settings. val config = Bundle().apply { putInt("KEY_DURATION_MS", 15000) // 15 seconds putInt("KEY_SIZE_KB", 20480) // 20 MB buffer size // Collect sampled CPU call stacks alongside the system trace. putBoolean("KEY_COLLECT_STACK_SAMPLING", true) putInt("KEY_FREQUENCY_HZ", 100) // Sampling frequency in Hz // Optional: Set KEY_SAMPLE_BINDER_ONLY to true to sample only binder calls. } profilingManager?.requestProfiling( ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE, config, "trace-with-callstacks", null, callbackExecutor, resultCallback )
জাভা
ProfilingManager profilingManager = context.getSystemService(ProfilingManager.class); if (profilingManager == null) return; // Pass a Bundle to customize the capture, or pass null for default settings. Bundle config = new Bundle(); config.putInt("KEY_DURATION_MS", 15000); // 15 seconds config.putInt("KEY_SIZE_KB", 20480); // 20 MB buffer size // Collect sampled CPU call stacks alongside the system trace. config.putBoolean("KEY_COLLECT_STACK_SAMPLING", true); config.putInt("KEY_FREQUENCY_HZ", 100); // Sampling frequency in Hz // Optional: Set KEY_SAMPLE_BINDER_ONLY to true to sample only binder calls. profilingManager.requestProfiling( ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE, config, "trace-with-callstacks", null, callbackExecutor, resultCallback );
Perfetto UI-তে ফলাফল প্রোফাইল খুললে, স্যাম্পেল করা কল স্ট্যাক আপনার অ্যাপের সাধারণ ট্রেস স্লাইসের পাশাপাশি ডেডিকেটেড কল স্ট্যাক ট্র্যাক হিসেবে দেখা যায়। কল স্ট্যাক ট্র্যাকের কোনও অঞ্চল বেছে নিয়ে সেই সময়সীমার জন্য স্যাম্পেল করা কল স্ট্যাকের ফ্লেম গ্রাফ দেখুন, যেমনটি চিত্র ১-এ দেখানো হয়েছে।
Java হিপ ডাম্প ক্যাপচার করুন
আপনি
JavaHeapDumpRequestBuilder (বা PROFILING_TYPE_JAVA_HEAP_DUMP) ব্যবহার করে আপনার অ্যাপের Java মেমরির স্ন্যাপশটের জন্য অনুরোধ করতে পারেন।
Debug.dumpHprofData-এর মাধ্যমে ক্যাপচার করা স্ট্যান্ডার্ড .hprof হিপ ডাম্পের মতো নয়,
ProfilingManager হিপ ডাম্প প্রোডাকশনে নিরাপদ, কম-ওভারহেড সংগ্রহের জন্য ডিজাইন করা হয়েছে:
- নন-ফ্রিজিং কালেকশন: সিস্টেম আপনার অ্যাপের প্রসেসের একটি কপি-অন-রাইট ক্লোন ফর্ক করে এবং ক্লোন করা প্রসেস থেকে হিপ স্ন্যাপশট সংগ্রহ করে, যাতে ডাম্প ডিস্কে লেখার সময় আপনার অ্যাপ ফ্রিজ না হয়।
- শুধু রেফারেন্স গ্রাফ (কোনও র অবজেক্ট ডেটা নেই):
.hprofফাইলের মতো র মেমরি কন্টেন্ট ডাম্প করার পরিবর্তে,ProfilingManagerএকটি.perfetto-java-heap-dumpপ্রোটোবাফ গ্রাফ রেকর্ড করে যাতে ক্লাস, অবজেক্ট সাইজ, রেফারেন্স চেইন এবং গার্বেজ কালেকশন (GC) রুট থাকে, তবে প্রিমিটিভ ভ্যালু, স্ট্রিং কন্টেন্ট এবং অ্যারে পেলোড বাদ দেওয়া হয়। এটি ব্যবহারকারীর গোপনীয়তা রক্ষা করে এবং ফাইলের সাইজ কমায়। সম্পূর্ণ ব্রেকডাউনের জন্য, ProfilingManager হিপ ডাম্প HPROF ডাম্প থেকে কীভাবে আলাদা তা দেখুন।
সিস্টেম ট্রেস রেকর্ড করুন-এ দেখানো একই Context, Executor এবং resultCallback সেট-আপ ব্যবহার করে,
JavaHeapDumpRequestBuilder-এর সাথে একটি অনুরোধ তৈরি করুন:
Kotlin
val heapDumpRequest = JavaHeapDumpRequestBuilder() .setTag("memory-leak-check") .build() requestProfiling( appContext, heapDumpRequest, callbackExecutor, resultCallback )
জাভা
ProfilingRequest heapDumpRequest = new JavaHeapDumpRequestBuilder() .setTag("memory-leak-check") .build(); Profiling.requestProfiling( getApplicationContext(), heapDumpRequest, callbackExecutor, resultCallback );
মেমরি লিক ডায়াগনসিস করার জন্য Java হিপ ডাম্প বিশ্লেষণ করার সম্পূর্ণ এন্ড-টু-এন্ড ওয়াকথ্রু দেখতে, কেস স্টাডি: OOM ক্র্যাশ ডিবাগ করা দেখুন।
পরবর্তী ধাপ
প্রোফাইল ক্যাপচার করার পরে, adb-এর মাধ্যমে সেগুলি লোকাল স্টোরেজে ফিরিয়ে আনুন অথবা
WorkManager-এর মাধ্যমে আপনার ব্যাকএন্ডে আপলোড করুন।