پروفایلینگ مبتنی بر تریگر

ProfilingManager از ثبت پروفایل‌ها بر اساس محرک‌های سیستم پشتیبانی می‌کند. سیستم فرآیند ضبط را مدیریت می‌کند و پروفایل حاصل را در اختیار برنامه شما قرار می‌دهد.

تریگرها به رویدادهای حیاتی عملکرد گره خورده‌اند. پروفایل‌های ثبت‌شده در سیستم، اطلاعات اشکال‌زدایی دقیقی را برای سفرهای حیاتی کاربر (CUJ) مرتبط با این تریگرها ارائه می‌دهند.

ثبت داده‌های تاریخی

بسیاری از محرک‌ها نیاز به تجزیه و تحلیل داده‌های تاریخی منتهی به رویداد دارند. خود محرک اغلب پیامد یک مشکل است تا علت اصلی. اگر فقط پس از وقوع محرک، یک پروفایل ایجاد کنید، ممکن است علت اصلی از بین رفته باشد.

برای مثال، یک عملیات طولانی مدت در نخ رابط کاربری باعث خطای عدم پاسخگویی برنامه (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. Get the manager : سرویس ProfilingManager را بازیابی می‌کند.
  2. تعریف یک تریگر : یک ProfilingTrigger برای TRIGGER_TYPE_APP_FULLY_DRAWN ایجاد می‌کند. این رویداد زمانی رخ می‌دهد که برنامه گزارش می‌دهد که راه‌اندازی آن به پایان رسیده و تعاملی است.
  3. محدودیت نرخ تنظیم : یک محدودیت نرخ ۱ ساعته برای این تریگر خاص اعمال می‌کند ( setRateLimitingPeriodHours(1) ). این کار مانع از ثبت بیش از یک پروفایل راه‌اندازی در هر ساعت توسط برنامه می‌شود.
  4. شنونده‌ی Register : متد 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() قابل دسترسی است، خصوصی می‌کند.

نموداری که نحوه عملکرد اسنپ‌شات‌های ردیابی پس‌زمینه را نشان می‌دهد، به همراه یک بافر حلقه‌ای که داده‌ها را قبل از یک رویداد ماشه ضبط می‌کند.
شکل 1 : نحوه عملکرد اسنپ‌شات‌های ردیابی پس‌زمینه.

برای کاهش تأثیر بر عملکرد دستگاه و عمر باتری، سیستم ردیابی‌های پس‌زمینه را به طور مداوم اجرا نمی‌کند. در عوض، از یک روش نمونه‌برداری استفاده می‌کند. سیستم به طور تصادفی ردیابی پس‌زمینه را در یک بازه زمانی مشخص (با حداقل و حداکثر مدت زمان) شروع می‌کند. فاصله‌گذاری تصادفی این ردیابی‌ها، پوشش تریگر را بهبود می‌بخشد.

پروفایل‌های فعال‌شده توسط سیستم، حداکثر اندازه‌ای دارند که توسط سیستم تعریف می‌شود، بنابراین از یک بافر حلقه‌ای استفاده می‌کنند. پس از پر شدن بافر، داده‌های ردیابی جدید، قدیمی‌ترین داده‌ها را بازنویسی می‌کنند. همانطور که در شکل 1 نشان داده شده است، اگر بافر پر شود، یک ردیابی ضبط‌شده ممکن است کل مدت زمان ضبط پس‌زمینه را پوشش ندهد. در عوض، جدیدترین فعالیت منتهی به فعال‌سازی را نشان می‌دهد.

محدود کردن سرعت مخصوص تریگر را پیاده‌سازی کنید

تریگرهای با فرکانس بالا می‌توانند به سرعت سهمیه محدودکننده نرخ برنامه شما را مصرف کنند. برای درک بهتر محدودکننده نرخ، توصیه می‌کنیم به نحوه عملکرد محدودکننده نرخ نگاهی بیندازید. برای جلوگیری از تمام شدن سهمیه شما توسط یک نوع تریگر، می‌توانید محدودیت نرخ مختص به تریگر را پیاده‌سازی کنید.

ProfilingManager از محدود کردن سرعت مخصوص تریگر که توسط برنامه تعریف می‌شود، پشتیبانی می‌کند. این به شما امکان می‌دهد علاوه بر محدودکننده سرعت موجود، لایه دیگری از تنظیم سرعت مبتنی بر زمان اضافه کنید. از API setRateLimitingPeriodHours برای تنظیم زمان توقف (cooldown) خاص برای یک تریگر استفاده کنید. پس از پایان زمان توقف، می‌توانید دوباره آن را فعال کنید.

اشکال‌زدایی به صورت محلی فعال می‌شود

از آنجا که ردیابی‌های پس‌زمینه در زمان‌های تصادفی اجرا می‌شوند، اشکال‌زدایی تریگرها به صورت محلی دشوار است. برای اعمال ردیابی پس‌زمینه برای آزمایش، از دستور ADB زیر استفاده کنید:

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

این دستور سیستم را مجبور می‌کند تا یک ردیابی پس‌زمینه مداوم برای بسته مشخص‌شده آغاز کند و به هر تریگر اجازه می‌دهد تا در صورت اجازه محدودکننده نرخ، یک پروفایل جمع‌آوری کند.

همچنین می‌توانید گزینه‌های اشکال‌زدایی دیگری را فعال کنید، برای مثال، هنگام اشکال‌زدایی محلی، محدودکننده نرخ را غیرفعال کنید. برای اطلاعات بیشتر، به دستورات اشکال‌زدایی برای پروفایل‌بندی محلی مراجعه کنید.