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

সিস্টেম ট্রিগারের উপর ভিত্তি করে প্রোফাইল ক্যাপচার করার সুবিধা 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. ট্রিগার যোগ করুন: ProfilingManager-এর সাথে ট্রিগার তালিকা রেজিস্টার করে addProfilingTriggers ব্যবহার করে।
  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>

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

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