অ্যাপ-চালিত প্রোফাইলিং

ProfilingManager API ব্যবহার করে কীভাবে সিস্টেম ট্রেস রেকর্ড করতে হয় তা এই পৃষ্ঠায় দেখানো হয়েছে।

এছাড়াও, ProfilingManager অন্যান্য ধরনের প্রোফাইল রেকর্ড করতে পারে। এই প্রসেসটি সিস্টেম ট্রেস রেকর্ড করার মতোই, তবে প্রতিটি ধরনের জন্য আলাদা আলাদা বিল্ডার ব্যবহার করা হয়। কাজ করে এমন প্রোফাইল ও সেগুলির বিল্ডার হল:

  • সিস্টেম ট্রেস: SystemTraceRequestBuilder ব্যবহার করে রেকর্ড করা হয়, যা লেটেন্সি বিশ্লেষণ ও সাধারণ পারফর্ম্যান্স ডিবাগিংয়ের জন্য উপযোগী।

  • হিপ ডাম্প: JavaHeapDumpRequestBuilder ব্যবহার করে রেকর্ড করা হয়, যা মেমরি লিক শনাক্তকরণ ও অপ্টিমাইজেশনের জন্য সহায়ক।

  • হিপ প্রোফাইল: HeapProfileRequestBuilder ব্যবহার করে রেকর্ড করা হয়, যা মেমরি অপ্টিমাইজেশনের জন্য সহায়ক।

  • কল স্ট্যাক প্রোফাইল: StackSamplingRequestBuilder ব্যবহার করে রেকর্ড করা হয়, যা কোড এক্সিকিউশন ও লেটেন্সি বিশ্লেষণ বোঝার জন্য উপযোগী।

ডিপেন্ডেন্সি যোগ করুন

ProfilingManager API-এর সাথে সেরা অভিজ্ঞতা পেতে, আপনার build.gradle.kts ফাইলে নিম্নলিখিত Jetpack লাইব্রেরি যোগ করুন।

Kotlin

   dependencies {
       implementation("androidx.tracing:tracing-ktx:2.0.4")
       implementation("androidx.core:core:1.19.1")
   }
   

গ্ৰুভি

   dependencies {
       implementation 'androidx.tracing:tracing:2.0.4'
       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 trace.
            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 heavyOperation() {
  // Computations you want to profile
}

void sampleRecordSystemTrace() {
  Executor mainExecutor = Executors.newSingleThreadExecutor();
  Consumer<ProfilingResult> resultCallback =
      new Consumer<ProfilingResult>() {
        @Override
        public void accept(ProfilingResult profilingResult) {
          if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.getResultFilePath());
            setupProfileUploadWorker(profilingResult.getResultFilePath());
          } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode="

                    + profilingResult.getErrorCode()
                    + " errormsg="
                    + profilingResult.getErrorMessage());
          }
        }
      };
  CancellationSignal stopSignal = new CancellationSignal();

  SystemTraceRequestBuilder requestBuilder = new SystemTraceRequestBuilder();
  requestBuilder.setCancellationSignal(stopSignal);
  requestBuilder.setTag("FOO");
  requestBuilder.setDurationMs(60000);
  requestBuilder.setBufferFillPolicy(BufferFillPolicy.RING_BUFFER);
  requestBuilder.setBufferSizeKb(32768);
  Profiling.requestProfiling(getApplicationContext(), requestBuilder.build(), mainExecutor,
      resultCallback);

  // Wait some time for profiling to start.

  Trace.beginSection("MyApp:HeavyOperation");
  heavyOperation();
  Trace.endSection();

  // Once the interesting code section is profiled, stop profile
  stopSignal.cancel();
}

স্যাম্পেল কোড নিম্নলিখিত ধাপগুলি অনুসরণ করে প্রোফাইলিং সেশন সেট-আপ এবং ম্যানেজ করে:

  1. এক্সিকিউটর সেট-আপ করুন। প্রোফাইলিং ফলাফল পাবে এমন থ্রেড সংজ্ঞায়িত করতে একটি Executor তৈরি করুন। ব্যাকগ্রাউন্ডে প্রোফাইলিং করা হয়। আপনি যদি পরে কলব্যাকে আরও প্রসেসিং যোগ করেন, তাহলে নন-UI থ্রেড এক্সিকিউটর ব্যবহার করলে তা অ্যাপ্লিকেশন সাড়া দিচ্ছে না (ANR) সংক্রান্ত সমস্যা প্রতিরোধ করতে সাহায্য করে।

  2. প্রোফাইলিং ফলাফল ম্যানেজ করা। Consumer<ProfilingResult> অবজেক্ট তৈরি করুন। সিস্টেম এই অবজেক্ট ব্যবহার করে ProfilingManager আপনার অ্যাপে প্রোফাইলিং ফলাফল পাঠায়।

  3. প্রোফাইলিং অনুরোধ তৈরি করুন। আপনার প্রোফাইলিং সেশন সেট-আপ করতে SystemTraceRequestBuilder তৈরি করুন। এই বিল্ডার আপনাকে ProfilingManager ট্রেস সেটিংস কাস্টমাইজ করতে দেয়। বিল্ডার কাস্টমাইজ করা ঐচ্ছিক; আপনি না করলে, সিস্টেম ডিফল্ট সেটিংস ব্যবহার করে।

    • ট্যাগ সংজ্ঞায়িত করুন। ট্রেসের নামে ট্যাগ যোগ করতে setTag() ব্যবহার করুন। এই ট্যাগ আপনাকে ট্রেস শনাক্ত করতে সাহায্য করে।
    • ঐচ্ছিক: সময়সীমা সেট করুন। মিলিসেকেন্ডে কতক্ষণ প্রোফাইল করতে হবে তা নির্দিষ্ট করতে setDurationMs() ব্যবহার করুন। যেমন, 60000 ৬০-সেকেন্ডের ট্রেস সেট করে। নির্দিষ্ট সময়সীমা শেষ হয়ে গেলে, ট্রেস অটোমেটিক বন্ধ হয়ে যায়, যদি CancellationSignal তার আগে ট্রিগার না হয়।
    • একটি বাফার নীতি বেছে নিন। ট্রেস ডেটা কীভাবে স্টোর করা হয় তা নির্ধারণ করতে setBufferFillPolicy() ব্যবহার করুন। BufferFillPolicy.RING_BUFFER-এর অর্থ হল, বাফার ভর্তি হয়ে গেলে, নতুন ডেটা সবচেয়ে পুরনো ডেটার উপর ওভাররাইট করে, যার ফলে সাম্প্রতিক অ্যাক্টিভিটির একটানা রেকর্ড রাখা যায়।
    • বাফার সাইজ সেট করুন। ট্রেসিংয়ের জন্য বাফার সাইজ নির্দিষ্ট করতে setBufferSizeKb() ব্যবহার করুন যা আপনি আউটপুট ট্রেস ফাইলের সাইজ কন্ট্রোল করতে ব্যবহার করতে পারবেন।
  4. ঐচ্ছিক: সেশন লাইফসাইকেল ম্যানেজ করুন। CancellationSignal তৈরি করুন। এই অবজেক্ট আপনাকে যখন খুশি প্রোফাইলিং সেশন বন্ধ করতে দেয়, যার ফলে আপনি এর দৈর্ঘ্যের উপর সুনির্দিষ্ট নিয়ন্ত্রণ পান।

  5. শুরু করুন এবং ফলাফল পান। আপনি requestProfiling()-এ কল করলে, ProfilingManager ব্যাকগ্রাউন্ডে প্রোফাইলিং সেশন শুরু করে। প্রোফাইলিং হয়ে গেলে, এটি আপনার resultCallback#accept পদ্ধতিতে ProfilingResult পাঠায়। প্রোফাইলিং সফলভাবে শেষ হলে, ProfilingResult সেই পাথ প্রদান করে যেখানে আপনার ডিভাইসে ট্রেস সেভ করা হয়েছিল ProfilingResult#getResultFilePath-এর মাধ্যমে। আপনি এই ফাইলটি প্রোগ্রাম্যাটিকভাবে পেতে পারেন অথবা, স্থানীয় প্রোফাইলিংয়ের জন্য, আপনার কম্পিউটার থেকে adb pull <trace_path> চালিয়ে।

  6. কাস্টম ট্রেস পয়েন্ট যোগ করুন। আপনার অ্যাপের কোডে কাস্টম ট্রেস পয়েন্ট যোগ করতে পারবেন। আগের কোডের উদাহরণে, trace("MyApp:HeavyOperation") { ... } ব্লকটি তৈরি করা প্রোফাইলে একটি কাস্টম স্লাইস তৈরি করে।