ট্রিগার-ভিত্তিক প্রোফাইলিং

ProfilingManager সিস্টেম ট্রিগারের উপর ভিত্তি করে প্রোফাইল ক্যাপচার করার সুবিধা দেয়। সিস্টেমটি রেকর্ডিং প্রক্রিয়া ম্যানেজ করে এবং আপনার অ্যাপে ফলাফল প্রোফাইল প্রদান করে।

ট্রিগার পারফর্ম্যান্স-ক্রিটিক্যাল ইভেন্টের সাথে যুক্ত। সিস্টেম-রেকর্ড করা প্রোফাইল এইসব ট্রিগারের সাথে সম্পর্কিত ক্রিটিকাল ইউজার জার্নির (CUJ) জন্য বিস্তারিত ডিবাগিং ইনফরমেশন প্রদান করে।

ঐতিহাসিক ডেটা ক্যাপচার করুন

অনেক ট্রিগারের ক্ষেত্রে ইভেন্ট পর্যন্ত অতীতের ডেটা বিশ্লেষণ করতে হয়। ট্রিগার নিজে প্রায়ই মূল কারণের পরিবর্তে কোনও সমস্যার ফলাফল হয়। ট্রিগার হওয়ার পরেই আপনি প্রোফাইল শুরু করলে, মূল কারণ আগেই হারিয়ে যেতে পারে।

যেমন, UI থ্রেডে দীর্ঘ সময় ধরে চলা কোনও অপারেশন অ্যাপ্লিকেশন সাড়া দিচ্ছে না (ANR) সংক্রান্ত সমস্যার কারণ হয়। সিস্টেম ANR শনাক্ত করে অ্যাপকে সিগন্যাল পাঠানোর মধ্যে অপারেশনটি সম্পূর্ণ হয়ে যেতে পারে। সেই মুহূর্তে প্রোফাইল শুরু করলে, প্রকৃত ব্লকিংয়ের কাজ মিস হয়ে যায়।

কিছু ট্রিগার ঠিক কখন ঘটবে তা অনুমান করা সম্ভব নয়, তাই আগে থেকে ম্যানুয়ালি প্রোফাইল শুরু করা অসম্ভব।

ট্রিগার-ভিত্তিক ক্যাপচার কেন ব্যবহার করবেন?

প্রোফাইলিং ট্রিগার ব্যবহার করার প্রাথমিক কারণ হল অপ্রত্যাশিত ইভেন্টের জন্য ডেটা ক্যাপচার করা, যেখানে এই ইভেন্টগুলি ঘটার আগে কোনও অ্যাপের পক্ষে ম্যানুয়ালি রেকর্ডিং শুরু করা অসম্ভব। প্রোফাইলিং ট্রিগার ব্যবহার করে এগুলি করা যেতে পারে:

  • পারফর্ম্যান্স সংক্রান্ত সমস্যার ডিবাগ করা: ANR, মেমরি লিক এবং অন্যান্য স্টেবিলিটি সংক্রান্ত সমস্যার ডায়াগনসিস করা।
  • ক্রিটিকাল ইউজার জার্নি অপ্টিমাইজ করা: ফ্লো বিশ্লেষণ ও উন্নত করা, যেমন, অ্যাপ স্টার্টআপ।
  • ব্যবহারকারীর আচরণ বোঝা: ইভেন্ট সম্পর্কে ইনসাইট পান, যেমন, ব্যবহারকারীর দ্বারা শুরু করা অ্যাপ এক্সিট।

ট্রিগার সেট-আপ করা

TRIGGER_TYPE_APP_FULLY_DRAWN ট্রিগারের জন্য কীভাবে রেজিস্টার করতে হয় এবং এতে রেট লিমিটিং প্রয়োগ করতে হয় তা নিম্নলিখিত কোড থেকে বোঝা যাবে।

Kotlin

fun recordWithTrigger() {
    val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java)

    val triggers = ArrayList<ProfilingTrigger>()

    val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN)
        .setRateLimitingPeriodHours(1)

    triggers.add(triggerBuilder.build())

    val mainExecutor: Executor = Executors.newSingleThreadExecutor()

    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.resultFilePath
            )
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode=" + profilingResult.errorCode + " errormsg=" + profilingResult.errorMessage
            )
        }
    }

    profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

}

জাভা

public void recordWithTrigger() {
  ProfilingManager profilingManager = getApplicationContext().getSystemService(
      ProfilingManager.class);
  List<ProfilingTrigger> triggers = new ArrayList<>();
  ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder(
      ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN);
  triggerBuilder.setRateLimitingPeriodHours(1);
  triggers.add(triggerBuilder.build());

  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());
          }
        }
      };
  profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

}

কোডটি এইসব ধাপ পারফর্ম করে:

  1. ম্যানেজার পান: ProfilingManager পরিষেবা ফিরিয়ে আনে।
  2. ট্রিগার নির্দিষ্ট করুন: TRIGGER_TYPE_APP_FULLY_DRAWN-এর জন্য ProfilingTrigger তৈরি করে। অ্যাপ যখন রিপোর্ট করে যে এটি স্টার্ট-আপ সম্পূর্ণ করেছে এবং ইন্টার‍্যাক্টিভ, তখন এই ইভেন্টটি ঘটে।
  3. রেট লিমিট সেট করা: এই নির্দিষ্ট ট্রিগারে ১ ঘণ্টার রেট লিমিট প্রয়োগ করে (setRateLimitingPeriodHours(1))। এটি অ্যাপকে প্রতি ঘণ্টায় একাধিক স্টার্টআপ প্রোফাইল রেকর্ড করা থেকে বিরত রাখে।
  4. লিসনার রেজিস্টার করুন: ফলাফল ম্যানেজ করে এমন কলব্যাক নির্ধারণ করতে registerForAllProfilingResults কল করে। এই কলব্যাক getResultFilePath()-এর মাধ্যমে সেভ করা প্রোফাইলের পাথ পায়।
  5. ট্রিগার যোগ করুন: addProfilingTriggers ব্যবহার করে ProfilingManager-এর সাথে ট্রিগার তালিকা রেজিস্টার করে।
  6. ফায়ার ইভেন্ট: reportFullyDrawn()-কে কল করে, যা সিস্টেমের জন্য TRIGGER_TYPE_APP_FULLY_DRAWN ইভেন্ট এমিট করে। এটি সিস্টেম ব্যাকগ্রাউন্ড ট্রেস চলছে এবং রেট লিমিটার কোটা উপলভ্য আছে ধরে নিয়ে প্রোফাইল কালেকশন ট্রিগার করে। এই ঐচ্ছিক ধাপটি একটি এন্ড-টু-এন্ড ফ্লো দেখায় কারণ এই ট্রিগারের জন্য আপনার অ্যাপকে অবশ্যই reportFullyDrawn() কল করতে হবে।

ট্রেস রিট্রিভ করুন

সিস্টেম অন্যান্য প্রোফাইলের মতো একই ডিরেক্টরিতে ট্রিগার-ভিত্তিক প্রোফাইল সেভ করে। ট্রিগার করা ট্রেসের জন্য ফাইলের নাম এই ফর্ম্যাট অনুসরণ করে:

profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>

আপনি ADB ব্যবহার করে ফাইলটি পুল করতে পারবেন। যেমন, ADB ব্যবহার করে উদাহরণ কোড-এর মাধ্যমে ক্যাপচার করা সিস্টেম ট্রেস পুল করতে, এটি এরকম দেখতে হতে পারে:

adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace

এইসব ট্রেস ভিজ্যুয়ালাইজ করার ব্যাপারে আরও জানতে, প্রোফাইলিং ডেটা ফিরিয়ে আনা ও বিশ্লেষণ করা দেখুন।

ব্যাকগ্রাউন্ড ট্রেসিং কীভাবে কাজ করে

ট্রিগারের আগে থেকে ডেটা ক্যাপচার করতে, OS নির্দিষ্ট সময় অন্তর ব্যাকগ্রাউন্ড ট্রেস চালু করে। এই ব্যাকগ্রাউন্ড ট্রেস অ্যাক্টিভ থাকাকালীন কোনও ট্রিগার হলে এবং আপনার অ্যাপ সেটির জন্য রেজিস্টার করা থাকলে, সিস্টেম আপনার অ্যাপের ডিরেক্টরিতে ট্রেস প্রোফাইল সেভ করে। প্রোফাইলে তখন এমন তথ্য অন্তর্ভুক্ত থাকবে যা ট্রিগার পর্যন্ত নিয়ে যায়।

প্রোফাইল সেভ হয়ে গেলে, সিস্টেম registerForAllProfilingResults-এ দেওয়া কলব্যাক ব্যবহার করে আপনার অ্যাপকে বিজ্ঞপ্তি পাঠায়। এই কলব্যাক ক্যাপচার করা প্রোফাইলের পাথ প্রদান করে যা ProfilingResult#getResultFilePath() কল করে অ্যাক্সেস করা যেতে পারে।

ব্যাকগ্রাউন্ড ট্রেস স্ন্যাপশট কীভাবে কাজ করে তা ডায়াগ্রামের মাধ্যমে দেখানো হচ্ছে, যেখানে ট্রিগার ইভেন্টের আগে রিং বাফার ডেটা ক্যাপচার করছে।
ছবি ১: ব্যাকগ্রাউন্ড ট্রেস স্ন্যাপশট কীভাবে কাজ করে।

ডিভাইসের পারফর্ম্যান্স ও ব্যাটারির আয়ুর উপর প্রভাব কমাতে, সিস্টেম ব্যাকগ্রাউন্ড ট্রেস একটানা চালায় না। পরিবর্তে, এটি স্যাম্পলিং পদ্ধতি ব্যবহার করে। সিস্টেমটি সেট করা টাইমফ্রেমের মধ্যে (সর্বনিম্ন এবং সর্বাধিক সময়কাল সহ) ব্যাকগ্রাউন্ড ট্রেস র‍্যান্ডমভাবে শুরু করে। এইসব ট্রেস র‍্যান্ডমভাবে স্পেস করলে ট্রিগার কভারেজ উন্নত হয়।

সিস্টেম-ট্রিগার করা প্রোফাইলে সিস্টেম-নির্ধারিত সর্বাধিক সাইজ থাকে, তাই সেগুলি রিং বাফার ব্যবহার করে। বাফার ভর্তি হয়ে গেলে, নতুন ট্রেস ডেটা সবচেয়ে পুরনো ডেটাকে ওভাররাইট করে। ছবি ১-এ যেমন দেখানো হয়েছে, বাফার ভর্তি হয়ে গেলে ক্যাপচার করা ট্রেস ব্যাকগ্রাউন্ড রেকর্ডিংয়ের সম্পূর্ণ সময়সীমা কভার নাও করতে পারে; পরিবর্তে, এটি ট্রিগার পর্যন্ত সবচেয়ে সাম্প্রতিক অ্যাক্টিভিটি দেখায়।

ট্রিগার-নির্দিষ্ট রেট লিমিটিং প্রয়োগ করা

হাই-ফ্রিকোয়েন্সি ট্রিগার আপনার অ্যাপের রেট লিমিট কোটা দ্রুত শেষ করে দিতে পারে। রেট লিমিটার সম্পর্কে আরও ভালোভাবে বুঝতে, আমরা আপনাকে রেট লিমিটার কীভাবে কাজ করে তা দেখতে উৎসাহিত করি। একটি ট্রিগার টাইপ যাতে আপনার কোটা শেষ করে না দেয়, তার জন্য আপনি ট্রিগার-নির্দিষ্ট রেট লিমিট প্রয়োগ করতে পারেন।

ProfilingManager অ্যাপ-নির্ধারিত ট্রিগার-নির্দিষ্ট রেট সীমিতকরণকে সমর্থন করে। এটি আপনাকে আগে থেকে থাকা রেট লিমিটরের সাথে সময়-ভিত্তিক থ্রটলিংয়ের আরেকটি লেয়ার যোগ করতে দেয়। কোনও ট্রিগারের জন্য নির্দিষ্ট কুলডাউন সময় সেট করতে setRateLimitingPeriodHours API ব্যবহার করুন। কুলডাউনের মেয়াদ শেষ হয়ে গেলে, আপনি এটি আবার ট্রিগার করতে পারবেন।

ডিবাগ স্থানীয়ভাবে ট্রিগার করে

ব্যাকগ্রাউন্ড ট্রেস যেকোনও সময় রান করে বলে, লোকালি ট্রিগার ডিবাগ করা কঠিন। পরীক্ষা করার জন্য ব্যাকগ্রাউন্ড ট্রেস করতে বাধ্য করতে, নিম্নলিখিত ADB কমান্ড ব্যবহার করুন:

adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>

এই কমান্ড সিস্টেমকে নির্দিষ্ট প্যাকেজের জন্য একটি অবিচ্ছিন্ন ব্যাকগ্রাউন্ড ট্রেস শুরু করতে বাধ্য করে, রেট লিমিটার অনুমতি দিলে প্রতিটি ট্রিগারকে একটি প্রোফাইল সংগ্রহ করার অনুমতি দেয়।

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