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

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

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

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

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

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

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

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

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

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

একটি ট্রিগার সেট আপ করুন

নিম্নলিখিত কোডটিতে দেখানো হয়েছে কিভাবে TRIGGER_TYPE_APP_FULLY_DRAWN ট্রিগারটির জন্য নিবন্ধন করতে হয় এবং এতে রেট লিমিটিং প্রয়োগ করতে হয়।

কোটলিন

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

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

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

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

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

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

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

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

ট্রিগার-নির্দিষ্ট হার সীমিতকরণ প্রয়োগ করুন

ঘন ঘন ব্যবহৃত ট্রিগারগুলো আপনার অ্যাপের রেট লিমিটার কোটা দ্রুত শেষ করে ফেলতে পারে। রেট লিমিটার সম্পর্কে আরও ভালোভাবে বোঝার জন্য, আমরা আপনাকে ‘রেট লিমিটার কীভাবে কাজ করে’ (How rate limiter works) অংশটি দেখার জন্য উৎসাহিত করছি। কোনো একটি নির্দিষ্ট ধরনের ট্রিগার যাতে আপনার কোটা শেষ করে না ফেলে, তা প্রতিরোধ করার জন্য আপনি ট্রিগার-ভিত্তিক রেট লিমিটিং প্রয়োগ করতে পারেন।

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

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

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

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

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

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