CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛
CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛
CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛

Android平台下的MethodTrace实现解析-免费源码丞旭猿

作者:卓_修武转载地址:https://juejin.cn/post/7107137302043820039

Android 中的MethodTrace

对于开发者来说,Android的Java 层 提供了两种开发者可直接调用的 Method Trace 的API,一是 android.os.Debug类中的 startMethodTracing相关API,第二个 是android.os.Trace 类中的beginSection相关AP。 这两者的区别是 Debug类只能监控 Java函数调用, 而Trace类底层是使用atrace实现,其追踪的函数会包含了应用及系统的Java 和Native函数,并且底层基于ftrace还可以追踪cpu的详细活动信息。

本文主要是为了分析Java层的Method Trace,因此主要研究 Debug类的 startMethodTracing的底层实现。 在拓展部分 介绍的 Java堆栈采样生成火焰图的方式 已开源到GitHub上 供参考github.com/Knight-ZXW/…

系统 Method Trace 实现解析

直接进入主题 ,本节会将Method Trace 分位 启动Trace、Trace进行中、结束Trace三个阶段进行分析。

启动Trace

当调用Debug.startMethodTracing 函数开始Trace时,其在native层的实际调用的API为 art/runtime/http://trace.ccTrace::Start()函数,该函数参数包含以下入参: 写入的File指针,buffer大小,flags信息、TraceOutputMode(输出模式)、TraceMode(trace实现方式)。 该函数核心的逻辑在于 需要根据TraceMode的值 采取不同的函数调用监听方案。 TraceMode的定义只有两种,分为kMethodTracing以及kSampling, 分别对应在Java层调用Debug.startMethodTracing 以及 Debug.startMethodTracingSamping 。

void Trace::Start(std::unique_ptr&& trace_file_in,
                  size_t buffer_size,
                  int flags,
                  TraceOutputMode output_mode,
                  TraceMode trace_mode,
                  int interval_us)

  //.. 省略
  //create Trace  
  {
    // Required since EnableMethodTracing calls ConfigureStubs which visits class linker classes.
    gc::ScopedGCCriticalSection gcs(self,
                                    gc::kGcCauseInstrumentation,
                                    gc::kCollectorTypeInstrumentation);
    ScopedSuspendAll ssa(__FUNCTION__);
    MutexLock mu(self, *Locks::trace_lock_);
    if (the_trace_ != nullptr) {
      //已经存在trace 实例,忽略本次调用 
      LOG(ERROR) << "Trace already in progress, ignoring this request";

    } else {
      enable_stats = (flags & kTraceCountAllocs) != 0;
      the_trace_ = new Trace(trace_file.release(), buffer_size, flags, output_mode, trace_mode);
      if (trace_mode == TraceMode::kSampling) {
        CHECK_PTHREAD_CALL(pthread_create, (&sampling_pthread_, nullptr, &RunSamplingThread,
                                            reinterpret_cast(interval_us)),
                                            "Sampling profiler thread");
        the_trace_->interval_us_ = interval_us;
      } else {
        runtime->GetInstrumentation()->AddListener(
            the_trace_,
            instrumentation::Instrumentation::kMethodEntered |
                instrumentation::Instrumentation::kMethodExited |
                instrumentation::Instrumentation::kMethodUnwind);
        // TODO: In full-PIC mode, we dont need to fully deopt.
        // TODO: We can only use trampoline entrypoints if we are java-debuggable since in that case
        // we know that inlining and other problematic optimizations are disabled. We might just
        // want to use the trampolines anyway since it is faster. It makes the story with disabling
        // jit-gc more complex though.
        runtime->GetInstrumentation()->EnableMethodTracing(
            kTracerInstrumentationKey, /*needs_interpreter=*/!runtime->IsJavaDebuggable());
      }
    }
  }}

if (the_trace_ != nullptr) {
      //已经存在trace 实例,忽略本次调用 
      LOG(ERROR) << "Trace already in progress, ignoring this request";

    }

在实现中,可以看到 在创建Trace实例时,如果判断当前已经存在trace实例(the_trace 变量),则会忽略这次调用。如果不存在,才会调用Trace构造函数函数,创建出trace实例,因此在一次Trace流程未结束前,多次调用StartTrace是无效的。

当真正开始创建Trace实例时,是通用 new Trace()创建的,创建实例之后,开始根据不同的 TraceMode,进行真正的函数调用监听实现。 这里根据TraceMode 分为了 采样类型(TraceMode::kSampling) 以及 插桩类型(TraceMode::kMethodTracing)的方式。

Trace 过程

采样类型 Trace

先关注下采样类型的实现,首先会通过 pthread_create 创建一个采样工作线程,这个线程执行的是Trace::RunSamplingThread(void*arg) 函数,在该函数内部会定期通过 Runtime对象的GetThreadList获取所有的线程,

之后遍历每个线程 执行 GetSample函数 获取每个线程当前的调用栈。

void* Trace::RunSamplingThread(void* arg) {
  Runtime* runtime = Runtime::Current();
  intptr_t interval_us = reinterpret_cast(arg);

  while (true) {
    //..省略
    {
     //..
      runtime->GetThreadList()->ForEach(GetSample, the_trace);
    }
  }

  runtime->DetachCurrentThread();
  return nullptr;
}

继续追踪GetSample函数的具体实现, 在该函数内部会通过 StackVisitor::WalkStack 进行栈回溯 从而获取当前的调用栈信息并保存在 stack_trace中,之后会调用the_trace->CompareAndUpdateStackTrace(thread, stack_trace);进行数据处理

static void GetSample(Thread* thread, void* arg) REQUIRES_SHARED(Locks::mutator_lock_) {
  std::vector* const stack_trace = Trace::AllocStackTrace();
  StackVisitor::WalkStack(
      [&](const art::StackVisitor* stack_visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
        ArtMethod* m = stack_visitor->GetMethod();
        // Ignore runtime frames (in particular callee save).
        if (!m->IsRuntimeMethod()) {
          stack_trace->push_back(m);
        }
        return true;
      },
      thread,
      /* context= */ nullptr,
      art::StackVisitor::StackWalkKind::kIncludeInlinedFrames);
  Trace* the_trace = reinterpret_cast(arg);

  //更新对应线程的 StackSapmle  
  the_trace->CompareAndUpdateStackTrace(thread, stack_trace);
}

继续分析 CompareAndUpdateStackTrace函数 ,在这个函数中,主要的工作是基于堆栈对比,来判断并写入函数的状态变更。

void Trace::CompareAndUpdateStackTrace(Thread* thread,
                                       std::vector* stack_trace) {
  CHECK_EQ(pthread_self(), sampling_pthread_);
  std::vector* old_stack_trace = thread->GetStackTraceSample();
  // Update the threads stack trace sample.
  thread->SetStackTraceSample(stack_trace);
  // Read timer clocks to use for all events in this trace.
  uint32_t thread_clock_diff = 0;
  uint32_t wall_clock_diff = 0;
  ReadClocks(thread, &thread_clock_diff, &wall_clock_diff);
  if (old_stack_trace == nullptr) {
    // If theres no previous stack trace sample for this thread, log an entry event for all
    // methods in the trace.
    for (auto rit = stack_trace->rbegin(); rit != stack_trace->rend(); ++rit) {
      LogMethodTraceEvent(thread, *rit, instrumentation::Instrumentation::kMethodEntered,
                          thread_clock_diff, wall_clock_diff);
    }
  } else {
    // If theres a previous stack trace for this thread, diff the traces and emit entry and exit
    // events accordingly.
    auto old_rit = old_stack_trace->rbegin();
    auto rit = stack_trace->rbegin();
    // Iterate bottom-up over both traces until theres a difference between them.
    while (old_rit != old_stack_trace->rend() && rit != stack_trace->rend() && *old_rit == *rit) {
      old_rit++;
      rit++;
    }
    // Iterate top-down over the old trace until the point where they differ, emitting exit events.
    for (auto old_it = old_stack_trace->begin(); old_it != old_rit.base(); ++old_it) {
      LogMethodTraceEvent(thread, *old_it, instrumentation::Instrumentation::kMethodExited,
                          thread_clock_diff, wall_clock_diff);
    }
    // Iterate bottom-up over the new trace from the point where they differ, emitting entry events.
    for (; rit != stack_trace->rend(); ++rit) {
      LogMethodTraceEvent(thread, *rit, instrumentation::Instrumentation::kMethodEntered,
                          thread_clock_diff, wall_clock_diff);
    }
    FreeStackTrace(old_stack_trace);
  }
}

主要的逻辑如下

  • 首先通过 thread->GetStackSampler获取上一次记录的 stackTraceSample, 并通过thread->SetStackTraceSample 记录该线程当前最新的stackTraceSample ;
  • 如果thread->GetStackSampler 获取上次的stackSample 为空,则直接遍历最新的stackTrace,并调用 LogMethodTraceEvent 记录函数事件,记录的函数事件类型 全部为kMethodEntered,表示这些函数已入栈
  • 如果thread->GetStackSampler 不为空,则通过比较最新的StackTrace 和上次的StackTrace,来记录 函数事件,主要逻辑如下
  • 从栈底遍历新旧StackTrace,分别找到栈帧不一致的开始点 记为 old_rit, rit
  • 针对旧的StackTrace ,从栈顶到old_rit 记录这部分函数的 EXIT 事件
  • 对于新的StackTrace ,从 rit 到栈顶 记录这部分函数的 Entered事件
  • 举个例子,比如上次的栈是A B C D E F G(顺序为栈底到栈顶),最新的是A B C H B C D,则认为D E F G函数POP了,并且又 PUSH了H B C D函数,每次采样间隔,只要这个函数没有被 POP,就可以认为这些函数(A B C 函数)耗时又增加了采样间隔的ms数;假设 B C 在第5次采样才被POP,采样周期为50ms,则可以认为 B C 函数耗时在 150ms ~250ms之间,采样方式能够获取的函数耗时精度取决于采样的周期,采样周期越短 越精准。

LogMethodTraceEvent函数内部 会执行实际 信息记录操作,每个函数事件所要记录的内容其所占的字节大小是固定的,在TraceClockSource 为kDual的情况下(表示同时使用WallTime 和 cpuTime记录), 每个函数事件通常会包含以下信息:

2个字节的线程ID + 4个字节的EncodeTraceMethodAndAction+ 4个字节的 thread_cpu_diff + 4个字节的 wall_clock_diff , 也就是说每个函数事件记录 占用14个字节,每个信息都是以小端格式写入。

void Trace::LogMethodTraceEvent(Thread* thread, ArtMethod* method,
                                instrumentation::Instrumentation::InstrumentationEvent event,
                                uint32_t thread_clock_diff, uint32_t wall_clock_diff) {
  // Ensure we always use the non-obsolete version of the method so that entry/exit events have the
  // same pointer value.
  method = method->GetNonObsoleteMethod();

  // Advance cur_offset_ atomically.
  int32_t new_offset;
  int32_t old_offset = 0;

  // In the non-streaming case, we do a busy loop here trying to get
  // an offset to write our record and advance cur_offset_ for the
  // next use.
  if (trace_output_mode_ != TraceOutputMode::kStreaming) {
    old_offset = cur_offset_.load(std::memory_order_relaxed);  // Speculative read
    do {
      new_offset = old_offset + GetRecordSize(clock_source_);
      if (static_cast(new_offset) > buffer_size_) {
        overflow_ = true;
        return;
      }
    } while (!cur_offset_.compare_exchange_weak(old_offset, new_offset, std::memory_order_relaxed));
  }

  TraceAction action = kTraceMethodEnter;
  switch (event) {
    case instrumentation::Instrumentation::kMethodEntered:
      action = kTraceMethodEnter;
      break;
    case instrumentation::Instrumentation::kMethodExited:
      action = kTraceMethodExit;
      break;
    case instrumentation::Instrumentation::kMethodUnwind:
      action = kTraceUnroll;
      break;
    default:
      UNIMPLEMENTED(FATAL) << "Unexpected event: " << event;
  }

  uint32_t method_value = EncodeTraceMethodAndAction(method, action);

  // Write data into the tracing buffer (if not streaming) or into a
  // small buffer on the stack (if streaming) which well put into the
  // tracing buffer below.
  //
  // These writes to the tracing buffer are synchronised with the
  // future reads that (only) occur under FinishTracing(). The callers
  // of FinishTracing() acquire locks and (implicitly) synchronise
  // the buffer memory.
  uint8_t* ptr;
  static constexpr size_t kPacketSize = 14U;  // The maximum size of data in a packet.
  uint8_t stack_buf[kPacketSize];             // Space to store a packet when in streaming mode.
  if (trace_output_mode_ == TraceOutputMode::kStreaming) {
    ptr = stack_buf;
  } else {
    ptr = buf_.get() + old_offset;
  }

  Append2LE(ptr, thread->GetTid());
  Append4LE(ptr + 2, method_value);
  ptr += 6;

  if (UseThreadCpuClock()) {
    Append4LE(ptr, thread_clock_diff);
    ptr += 4;
  }
  if (UseWallClock()) {
    Append4LE(ptr, wall_clock_diff);
  }
  static_assert(kPacketSize == 2 + 4 + 4 + 4, "Packet size incorrect.");

   WriteToBuf(stack_buf, sizeof(stack_buf));
  }
}

插桩类型 Trace

第二种追踪方式对应 TraceMode::kMethodTracing 模式,它使用了系统art/runtime/instrumentation的提供的

MethodTracing功能,当调用instrumentation->EnableMethodTracing函数时,其内部会通过调用Runtime->GetClassLinker->VisitClasses(&visitor)遍历所有的Class 的函数,并为每个函数安装 Stub ,来监听函数的进入和退出。

遍历所有Class,visitor实现为 InstallStaubsClassVisitor 从而为所有类执行安装stub操作

void Instrumentation::UpdateStubs() {
   //...
  UpdateInstrumentationLevel(requested_level);
  if (requested_level > InstrumentationLevel::kInstrumentNothing) {
    InstallStubsClassVisitor visitor(this);
    runtime->GetClassLinker()->VisitClasses(&visitor);
    //...
  } else {
    InstallStubsClassVisitor visitor(this);
    runtime->GetClassLinker()->VisitClasses(&visitor);
    MaybeRestoreInstrumentationStack();
  }
}

最终会为每个函数调用 InstallSubtsForMethod,实现函数Hook

void Instrumentation::InstallStubsForMethod(ArtMethod* method) {
  if (!method->IsInvokable() || method->IsProxyMethod()) {
    return;
  }

  if (IsProxyInit(method)) {
    return;
  }

  if (InterpretOnly(method)) {
    UpdateEntryPoints(method, GetQuickToInterpreterBridge());
    return;
  }

  if (EntryExitStubsInstalled()) {
    // Install the instrumentation entry point if needed.
    if (CodeNeedsEntryExitStub(method->GetEntryPointFromQuickCompiledCode(), method)) {
      UpdateEntryPoints(method, GetQuickInstrumentationEntryPoint());
    }
    return;
  }

  // Were being asked to restore the entrypoints after instrumentation.
  CHECK_EQ(instrumentation_level_, InstrumentationLevel::kInstrumentNothing);
  // We need to have the resolution stub still if the class is not initialized.
  if (NeedsClinitCheckBeforeCall(method) && !method->GetDeclaringClass()->IsVisiblyInitialized()) {
    UpdateEntryPoints(method, GetQuickResolutionStub());
    return;
  }
  UpdateEntryPoints(method, GetOptimizedCodeFor(method));
}

安装 stubs的具体实现稍微有些复杂,其具体的实现在本文不做详细分析。 这里举个例子,对于Quick编译的代码,需预先通过setMethodEntryHook及 setMethodExitHook 已经预留了钩子。

通过InstallSubtsForMethod 会为每个函数安装好对应的进入函数退出函数的钩子 ,在钩子的实现中,会分别调用 instrumenttation的MethodEnterEventMethodExitEvent, 最终会遍历 instrumentation注册的所有监听者,通过事件的方式告知 函数进入 和退出的发生。

void Instrumentation::MethodEnterEventImpl(Thread* thread, ArtMethod* method) const {
  if (HasMethodEntryListeners()) {
    for (InstrumentationListener* listener : method_entry_listeners_) {
      if (listener != nullptr) {
        listener->MethodEntered(thread, method);
      }
    }
  }
}

因此, 在Trace 实现中, 其通过 Instrumentation 提供的 AddListener函数 注册Listener , 最终实现了 函数进入和退出的监控

{   //当不是采样类型追踪时,执行的逻辑

        runtime->GetInstrumentation()->AddListener(
            the_trace_,
            instrumentation::Instrumentation::kMethodEntered |
                instrumentation::Instrumentation::kMethodExited |
                instrumentation::Instrumentation::kMethodUnwind);
        // TODO: In full-PIC mode, we dont need to fully deopt.
        // TODO: We can only use trampoline entrypoints if we are java-debuggable since in that case
        // we know that inlining and other problematic optimizations are disabled. We might just
        // want to use the trampolines anyway since it is faster. It makes the story with disabling
        // jit-gc more complex though.
        runtime->GetInstrumentation()->EnableMethodTracing(
            kTracerInstrumentationKey, /*needs_interpreter=*/!runtime->IsJavaDebuggable());
 }

在监听到函数的Enter和Exit后,执行的逻辑和 采样类型一样,都是调用LogMethodTraceEvent来记录信息

void Trace::MethodEntered(Thread* thread, ArtMethod* method) {
  uint32_t thread_clock_diff = 0;
  uint32_t wall_clock_diff = 0;
  ReadClocks(thread, &thread_clock_diff, &wall_clock_diff);
  LogMethodTraceEvent(thread, method, instrumentation::Instrumentation::kMethodEntered,
                      thread_clock_diff, wall_clock_diff);
}

void Trace::MethodExited(Thread* thread,
                         ArtMethod* method,
                         instrumentation::OptionalFrame frame ATTRIBUTE_UNUSED,
                         JValue& return_value ATTRIBUTE_UNUSED) {
  uint32_t thread_clock_diff = 0;
  uint32_t wall_clock_diff = 0;
  ReadClocks(thread, &thread_clock_diff, &wall_clock_diff);
  LogMethodTraceEvent(thread,
                      method,
                      instrumentation::Instrumentation::kMethodExited,
                      thread_clock_diff,
                      wall_clock_diff);
}

Finish Trace

当Java层调用 Debug.stopMethodTracing() 时,最终会调用到 nativce层http://trace.ccFiniishingTracing函数,在该函数内部,会先进行 trace文件 Header部分的信息组装。

这部分首先会记录 trace文件的版本号、trace追踪的时间、时间度量的类型(wallTime 还是 cpuTime),函数调用的次数 、虚拟机类型、进程号等基础信息。由于在记录函数创建事件时,并不是直接记录每个函数的名称 。而是记录内部生成的ID,因此需要将这部分映射关系记录在Header中, 具体的实现是通过调用DumpMethodList 来记录的

void Trace::FinishTracing() {
  size_t final_offset = 0;
  std::set visited_methods;
  if (trace_output_mode_ == TraceOutputMode::kStreaming) {
    // Clean up.
    MutexLock mu(Thread::Current(), *streaming_lock_);
    STLDeleteValues(&seen_methods_);
  } else {
    final_offset = cur_offset_.load(std::memory_order_relaxed);
    GetVisitedMethods(final_offset, &visited_methods);
  }

  // Compute elapsed time.
  uint64_t elapsed = MicroTime() - start_time_;

  std::ostringstream os;
  //记录Trace版本
  os << StringPrintf("%cversion\n", kTraceTokenChar);
  os << StringPrintf("%d\n", GetTraceVersion(clock_source_));

  os << StringPrintf("data-file-overflow=%s\n", overflow_ ? "true" : "false");
  //记录时钟类型  时及race间
  if (UseThreadCpuClock()) {
    if (UseWallClock()) {
      os << StringPrintf("clock=dual\n");
    } else {
      os << StringPrintf("clock=thread-cpu\n");
    }
  } else {
    os << StringPrintf("clock=wall\n");
  }
  os << StringPrintf("elapsed-time-usec=%" PRIu64 "\n", elapsed);  
  if (trace_output_mode_ != TraceOutputMode::kStreaming) {
    size_t num_records = (final_offset - kTraceHeaderLength) / GetRecordSize(clock_source_);
    os << StringPrintf("num-method-calls=%zd\n", num_records);
  }
  os << StringPrintf("clock-call-overhead-nsec=%d\n", clock_overhead_ns_);
  os << StringPrintf("vm=art\n");
  os << StringPrintf("pid=%d\n", getpid());
  if ((flags_ & kTraceCountAllocs) != 0) {
    os << "alloc-count=" << Runtime::Current()->GetStat(KIND_ALLOCATED_OBJECTS) << "\n";
    os << "alloc-size=" << Runtime::Current()->GetStat(KIND_ALLOCATED_BYTES) << "\n";
    os << "gc-count=" <<  Runtime::Current()->GetStat(KIND_GC_INVOCATIONS) << "\n";
  }
  // 记录线程信息  
  os << StringPrintf("%cthreads\n", kTraceTokenChar);
  DumpThreadList(os);
  //记录函数信息  
  os << StringPrintf("%cmethods\n", kTraceTokenChar);
  DumpMethodList(os, visited_methods);
  os << StringPrintf("%cend\n", kTraceTokenChar);
  std::string header(os.str());
  //.....  
}

MethodList记录的每个函数信息结构为methodId prettyMethodDescritpor methodName methodSignature methodDeclaringClassSourceFile,这里的methodId 是 Trace追踪过程中生成的,为每个记录的函数分配的唯一ID。

同样的,由于在 method trace过程中记录的是线程ID,而不是线程名,因此需要通过DumpThreadList记录 线程id和线程名的映射关系。由于头文件信息是在Trace结束时才开始记录的,对于线程来说,可能存在 Trace过程中有线程退出的情况, 因此为了保证 退出的线程也能被记录,在 art/runtime/thread_list.cc 中 ,当线程退出时,在ThreadList::Unregister(Thread* self)内部, 会专门通过 调用Trace::StoreExistingThreadInfo提前记录下来。

最终生成的 Trace Header 为类似的文本信息

Trace 方式对比

以上分析了ART虚拟机 methodTrace 在采样 和插桩模式下的实现方式,通过实现可以粗略对比出两种方式的优缺点。 对于插桩方式, 由于是精准跟踪每个函数的Enter Exit, 因此对于函数耗时的判断是最准确的,并且不会遗漏函数, 但是自身对性能的影响也更大; 而采样的方式对于函数的Enter Exit判断是基于两次样本的差异判断,对于一些耗时较低的函数,可能不会被记录在内,并且在计算函数耗时 时,精度也很采样间隔有关,不过其对性能的影响较低。

method trace方式性能影响精确度
插桩较高准确
采样一般

拓展

Android Studio对 Trace文件的处理

通过art/runtime/trace.cc生成的Trace文件,在拖入AS后,可以直接以Top Down 或者火焰图的形式展示, 因此研究了下这部分的代码。其中,对于Trace文件的解析源码位于 perflib项目中的VmTraceParse类 。在解析Trace文件后,每个函数信息会被转化为JavaMethodModel对象。 JavaMethodModel为CaputureNode 的子类, TopDownNode类 合并相同CaptureNode 对象,最终可以生成一个树结构,该结构即可以展示出 TopDown的效果.

AS中 FlameChart的数据结构 也是由TopDownNode 转换而来,因为本质上没什么区别,都是一个树形结构,

只不过,对于FlameChart展示方式来说,将更函数的耗时以更宽的长度展示出来

Method Trace的应用

从表现形式上来说,火焰图的展示方式通过为不同耗时函数展示不同的宽度,因此可以更快速的定位到耗时函数。我们通过上述的源码分析 已经通过指导,通过堆栈采样的方式可以实现耗时函数的监控。 本身在Java层,通过Thread.getStackTrace也可以实现堆栈采集的能力,那么不通过Native的能力,我们其实也可以实现一个简单的Method Trace方案。方案具体的实现很简单,开启后台线程,定时采集主线程的栈信息, 并默认保存最近 n秒的堆栈信息记录即可。

关于具体的应用场景,举几个目前已实践的案例,在APM系统中 当我们监测到 APP慢启动、慢消息处理、页面慢启动、ANR 时,很多情况下只能拿到最后时刻的一些信息,或者是一些简单的流程耗时,没法感知具体主线程在这阶段函数执行的情况, 通过补齐methodTrace 信息,将上述性能问题发生时间段的 堆栈样本一起上报,之后,在APM平台上,分析具体问题时,我们提供了展示性能问题发生时间段的火焰图。 这里以应用启动监控功能为例,对于慢启动的日志样本,可以展示出对于的火焰图信息

通过上面火焰图样例可以快速定位出,本次慢启动是 Webview.init 导致的。

卡顿监控案例分享

这里分享一个具体的代码实现案例,在线下场景中,以卡顿为例,我们可能更希望发生卡顿之后,能够立即受到通知,点击通知时,能够在手机上以火焰图的形式 直接展示卡顿时间段的堆栈。以下是最终实现的效果,代码已开源在githubgithub.com/Knight-ZXW/…

在Demo中目前只采集了wallTime,没有记录Thread CpuTime,后面有时间的话会完善一下

总结

本文首先 通过源码分析 粗略了解了 Android 系统 在Native层实现 Method Trace的方式,对于插桩方式,其最终是通过Instrementation AddListener监听函数的进入、退出事件实现的, 而采样的方式是通过开启线程定时执行,通过StackVisitor 获取所有线程函数栈的方式。 对于这两种方案,在线上模式 我们可以参考系统采样的方式,通过 Hook 并调用 StackVisitor相关API 实现线程堆栈的高性能采集方式。

在拓展部分,简单分享了 AndroidStudio 对Method Trace文件的处理,不管是 插桩的方式 还是采样的方式,最终在表现形式上,我们都可以通过火焰图的方式 快速定位 阻塞函数, 对于在不太了解Native层的情况下,我们也可以直接使用Java层 Thread类的 getStackTrace方式,采集线程当前的栈信息。 在Method Trace 的应用方面,本文演示基于堆栈采样在 Android 卡顿监控 (Looper Message 角度)的一个样例 ,最终可以在设备上 直接以火焰图的形式展示函数调用情况。

声明:本文部分素材转载自互联网,如有侵权立即删除 。

© 版权声明
THE END
喜欢就支持一下吧
点赞0赞赏 分享
相关推荐
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容