当前位置: 首页 > news >正文

Android perfetto 简介

  1. Android perfetto 简介

使用 perfetto 工具,您可以通过 Android 调试桥 (adb) 在 Android 设备上收集性能信息。使用 adb shell perfetto ... 命令调用 perfetto 工具。 perfetto 从您的设备上收集性能跟踪数据时会使用多种来源,例如:

  • 使用 ftrace 收集内核信息

  • 使用 atrace 收集服务和应用中的用户空间注释

  • 使用 heapprofd 收集服务和应用的本地内存使用情况信息

  1. Perfetto 系统框图介绍和运行原理

Perfetto 是一个高级的开源工具,专为性能监测和分析而设计。它配备了一整套服务和库,能够捕获和记录系统层面以及应用程序层面的活动数据。此外,Perfetto 还提供了内存分析工具,既适用于本地应用也适用于 Java 环境。它的一个强大功能是,可以通过 SQL 查询库来分析跟踪数据,让你能够深入理解性能数据背后的细节。为了更好地处理和理解大规模数据集,Perfetto 还提供了一个基于 Web 的用户界面,使你能够直观地可视化和探索多 GB 大小的跟踪文件。简而言之,Perfetto 是一个全面的解决方案,旨在帮助开发者和性能工程师以前所未有的深度和清晰度来分析和优化软件性能。

Perfetto 是完全兼容 Systrace 的。你之前抓的 Systrace 文件,可以直接扔到 Perfetto Viewer 网站里面直接打开。如果你还没有适应 Perfetto ,你也可以从 Perfetto Viewer 一键打开 Systrace。

下图是 Perfetto 的架构图,可以看到 Perfetto 包含了三大块:

  1. Record traces :即数据抓取模块,可以看到抓取的内容和来源非常丰富,Java、 Native 、Linux 都有涉及到,相比 Systrace 要丰富很多。

  2. Analyze traces :主要是 trace 分析模块,包括 Trace 解析、SQL 查询、Metrics 分析等,这部分有专门的命令行工具提供,方便大家直接调用或者在工具链里面去调用。

  3. Visualize Traces:Trace 的呈现、抓取等

Perfetto 工具就提供了这样的一个上帝视角,通过上帝视角我们可以看到 Android 系统在运行时的各个细节,比如

  1. Input 事件是怎么流转的

  2. 你正在使用的 App 的每一帧是怎么从 App 产生到上屏的

  3. CPU 的实时频率、负载、摆核、唤醒等

  4. 系统中的各个 App 是怎么运行的

  5. ....

App 开发者和 Android 系统开发者也都会在重要的代码逻辑处加上 Trace 点,打开部分 Debug 选项之后,更是可以得到非常详细的信息,甚至一个 Task 为什么摆在某个 cpu 上,都会有详细的记载。通过这些在 Perfetto 上所展示的信息,我们能初步分析到性能问题的原因,接下来继续分析就会有针对性

  1. perfetto 工具使用方法

3.1、使用perfetto抓取trace

 

atrace --list_categories 列出抓取的trace策略 示例: 1)perfetto抓trace文件 配置 atrace 类别列表。 adb shell perfetto gfx view wm am sm hal res rs ss -o /data/misc/perfetto-traces/trace -t 5s 或 adb shell perfetto gfx view wm am hal rs ss aidl power -o /data/misc/perfetto-traces/trace --app app包名 -t 5s 或 adb shell perfetto gfx input view wm am sm hal res rs ss aidl sched irq freq load workq binder_driver thermal -o /data/misc/perfetto-traces/trace -t 10s 2)导出trace文件 adb pull /data/misc/perfetto-traces/trace .

3.2、分析trace文件工具

 

Google Chrome浏览器打开https://ui.perfetto.dev 1)打开https://ui.perfetto.dev/网页后,点击open trace file,选择刚才抓取的trace文件。 2)搜索trace关键字 -> f -> shift + m标记 例如搜索:activityConfigChanged。 或者搜索自己添加的RootWindowContainer_onDisplayChanged等等等 示例: 1)在搜索框输入:RootWindowContainer_onDisplayChanged 点击左右箭头跳到上一处或下一处该关键字出现的地方。 2)点击f键会滚动并放大到刚才选择关键字的地方 3)点击m临时标记或者shift + m永久标记

3.3、添加自己的trace方法

 

1,framework层: import android.os.Trace; Trace.traceBegin(long traceTag, String methodName) Trace.traceEnd(long traceTag) 在代码中必须成对出现,一般将traceEnd放入到finally语句块,另外,必须在同一个线程。 例如: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg"); ... Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 2,app层 import android.os.Trace; android.os.Trace.beginSection("lqy111"); Log.d("systrace_log_for_app_lqy111"); android.os.Trace.endSection(); 3,native framework层 #define ATRACE_TAG xxx // xxx和当前相关代码有关 #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include<utils/Trace.h> ATRACE_CALL(); ATRACE_NAME("lqy111"); //#define ATRACE_CALL() ATRACE_NAME(__FUNCTION__) 或 ATRACE_BEGIN("lqy111"); ... ATRACE_END(); 或 #include <gui/TraceUtils.h> ATRACE_FORMAT("onMessageInvalidate %s", "hahaha"); 示例:AOSP在SurfaceFlinger.cpp添加的trace http://aospxref.com/android-12.0.0_r3/xref/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp #define ATRACE_TAG ATRACE_TAG_GRAPHICS ATRACE_FORMAT("onMessageInvalidate %" PRId64 " vsyncIn %.2fms%s", vsyncId, vsyncIn, mExpectedPresentTime == expectedVSyncTime ? "" : " (adjusted)"); 示例:AOSP在ActivityConfigurationChangeItem.java添加的trace http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java public void execute(ClientTransactionHandler client, ActivityClientRecord r, PendingTransactionActions pendingActions) { // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here. Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged"); client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); }

3.4、命令参数解析

下表列出了在 perfetto 的两种模式下都可使用的选项:

表 1. 可用的常规 perfetto 工具选项列表。

选项说明
--background |
-d
perfetto 立即退出命令行界面,并继续在后台记录您的跟踪数据。
--background-wait | -D与 --background 类似,但会在退出前等待(最多 30 秒)所有数据源启动。如果收到成功的确认,则退出代码为零,否则为非零(错误或超时)。
--alert-id触发此跟踪记录的警报的 ID。
--config-id触发配置的 ID。
--config-uid已注册配置的应用的 UID。
--subscription-id触发此跟踪记录的订阅的 ID。
--out OUT_FILE |
-o OUT_FILE
为输出跟踪文件或 stdout 的 - 指定所需的路径。 perfetto 将输出写入上述标志中所述的文件。输出格式将用 AOSP trace.proto 中定义的格式编译。注意:您必须指定输出文件的完整路径名。通常应将这些文件写入 /data/misc/perfetto-traces 文件夹。
--upload完成后,将轨迹传递给 proto 轨迹配置中的 IncidentReportConfig 消息指定的软件包。
--no-guardrails在测试中启用 --upload 标志时,停用防止过多使用资源的保护措施。
--reset-guardrails重置 guardrails 的持久状态并退出(用于测试)。
--rsave-for-bugreport如果正在运行 bugreport_score 大于 0 的跟踪记录,则系统会将跟踪记录保存到文件中。完成后输出路径。
--query查询服务状态,并将其输出为用户可读的文本。
--query-raw与 --query 类似,但会输出 tracing_service_state.proto. 的原始 proto 编码字节。
--help | -h输出 perfetto 工具的帮助文本。

在轻量模式下使用 perfetto 的一般语法如下:

 

adb shell perfetto [ --time TIMESPEC ] [ --buffer SIZE ] [ --size SIZE ] [ ATRACE_CAT | FTRACE_GROUP/FTRACE_NAME | FTRACE_GROUP/* ]... --out FILE adb shell perfetto [ --txt ] --config CONFIG_FILE --out FILE console:/data # perfetto -h [385.873] perfetto_cmd.cc:213 Usage: perfetto --background -d : Exits immediately and continues tracing in background --config -c : /path/to/trace/config/file or - for stdin --out -o : /path/to/out/trace/file or - for stdout --upload : Upload field trace (Android only) --dropbox TAG : DEPRECATED: Use --upload instead TAG should always be set to 'perfetto' --no-guardrails : Ignore guardrails triggered when using --upload (for testing). --txt : Parse config as pbtxt. Not for production use. Not a stable API. --reset-guardrails : Resets the state of the guardails and exits (for testing). --query : Queries the service state and prints it as human-readable text. --query-raw : Like --query, but prints raw proto-encoded bytes of tracing_service_state.proto. --save-for-bugreport : If a trace with bugreport_score > 0 is running, it saves it into a file. Outputs the path when done. --help -h light configuration flags: (only when NOT using -c/--config) --time -t : Trace duration N[s,m,h] (default: 10s) --buffer -b : Ring buffer size N[mb,gb] (default: 32mb) --size -s : Max file size N[mb,gb] (default: in-memory ring-buffer only) --app -a : Android (atrace) app name ATRACE_CAT : Record ATRACE_CAT (e.g. wm) FTRACE_GROUP/FTRACE_NAME : Record ftrace event (e.g. sched/sched_switch) statsd-specific flags: --alert-id : ID of the alert that triggered this trace. --config-id : ID of the triggering config. --config-uid : UID of app which registered the config. --subscription-id : ID of the subscription that triggered this trace. Detach mode. DISCOURAGED, read https://perfetto.dev/docs/concepts/detached-mode : --detach=key : Detach from the tracing session with the given key. --attach=key [--stop] : Re-attach to the session (optionally stop tracing once reattached). --is_detached=key : Check if the session can be re-attached (0:Yes, 2:No, 1:Error).

轻量模式的事件说明符列表。

事件说明
ATRACE_CAT指定您想为其记录跟踪数据的 atrace 类别。 例如,以下命令会使用 atrace 跟踪窗口管理器: adb shell perfetto --out FILE wm 如需记录其他类别,请参阅此 atrace 类别列表。
FTRACE_GROUP/FTRACE_NAME指定您想为其记录跟踪数据的 ftrace 事件。例如,以下命令会跟踪 sched/sched_switch 事件: adb shell perfetto --out FILE sched/sched_switch

atrace 类别列表获取 --list_categories

 

比如下面的 vm 参数,更多的使用# atrace --list_categories 查看 adb shell perfetto -o /data/trace_file_10 -t 10s sched freq idle am wm gfx binder_driver hal dalvik camera input res memory regulators mmc ion console:/data # atrace --list_categories gfx - Graphics input - Input view - View System webview - WebView wm - Window Manager am - Activity Manager sm - Sync Manager audio - Audio video - Video camera - Camera hal - Hardware Modules res - Resource Loading dalvik - Dalvik VM rs - RenderScript bionic - Bionic C Library power - Power Management pm - Package Manager ss - System Server database - Database network - Network adb - ADB vibrator - Vibrator aidl - AIDL calls nnapi - NNAPI rro - Runtime Resource Overlay pdx - PDX services sched - CPU Scheduling irq - IRQ Events i2c - I2C Events freq - CPU Frequency idle - CPU Idle disk - Disk I/O mmc - eMMC commands sync - Synchronization workq - Kernel Workqueues memreclaim - Kernel Memory Reclaim regulators - Voltage and Current Regulators binder_driver - Binder Kernel driver binder_lock - Binder global lock trace pagecache - Page cache memory - Memory thermal - Thermal event gfx - Graphics (HAL) ion - ION allocation (HAL)

选项说明
--config CONFIG_FILE | -c CONFIG_FILE指定配置文件的路径。在普通模式下,某些配置可能会在配置协议缓冲区中进行编码。此文件必须符合 AOSP trace_config.proto 中定义的协议缓冲区架构。根据 AOSP data_source_config.proto 中的定义,可以使用 TraceConfig 的 DataSourceConfig 成员来选择和配置数据源。
--txt指示 perfetto 将配置文件解析为 pbtxt。此标志仅用于本地测试,不建议将其用于生产环境。

  1. Perfetto 的核心优势和功能亮点:

通过长时间的使用和对比,以及看各种分享,总结了一下 Perfetto 的核心优势和功能两点

  1. 支持长时间数据抓取

    1. Perfetto 通过后台服务支持长时间数据抓取,利用 Protobuf 编码存储数据。

  2. 数据来源与兼容性

    1. 基于 Linux 内核的 Ftrace 机制,记录用户空间与内核空间的关键事件。

    2. 兼容 Systrace 的功能,并有望最终取代它。

  3. 全面的数据支持

    1. 支持 Trace、Metric 和 Log 类型的数据。

    2. 提供多种数据抓取方式,包括网页、命令行工具、开发者选项以及 Perfetto Trigger API。

  4. 高效的数据分析

    1. 提供数据可视化网页,支持大文件渲染,优于 Systrace。

    2. Trace 文件可转换为 SQLite 数据库文件,支持 SQL 查询和脚本执行。

    3. 提供 Python API,允许将数据导出为 DataFrame 格式,为深入分析提供便利。

    4. 支持函数调用堆栈展示。

    5. 支持内存堆栈展示。

    6. 支持 pin 住你感兴趣的行到最上面,不用一直上下翻(通过脚本可以一打开就自动 pin)

    7. 支持可视化 Binder 调用和跳转

    8. 支持很方便的查询唤醒源

    9. 支持 Critical Task 的可视化查询

其基本功能包括以下几部分:

  • 在安卓上记录痕迹

  • 在 Linux 上记录跟踪

  • 记录铬迹

  • SQL 分析和指标

  • 跟踪转换

  • 堆分析

  • 安卓上的调用堆栈采样

  1. Linux 内核的 Ftrace 机制

动态hook最核心的原理:

function trace的最基本原理是利用编译器特性在非inline函数、无#define notrace __attribute__((__no_instrument_function__))属性的函数,在函数入口处添加了_mcount钩子,并且赋值lr(x30)寄存器到x0,这样就可以在_mcount钩子里增加维测函数。

Ftrace 是Kernel的官方tracing 框架。初始开发者和maintainer是Steven Rostedt,在2008年合入Kernel。

功能不只有函数跟踪,还包含了静态trace event、动态trace event、stack trace、latency tracer等功能,是一个完善的trace 框架。同时提供统计、过滤、触发等功能,方便trace log的捕获和分析。

Ftrace使用per-cpu的trace buffer,buffer内容为二进制格式,执行效率高。设置CONFIG_DYNAMIC_FTRACE后,加入的trace功能在不使用时对系统性能几乎没有影响。

记录用户空间与内核空间的关键事件。

内核配置生成文件

 

out/target/product/msmnile_gvmq/obj/KERNEL_OBJ/kernel/config_data:6809:CONFIG_FTRACE=y out/target/product/msmnile_gvmq/obj/KERNEL_OBJ/.config:6809:CONFIG_FTRACE=y out/target/product/msmnile_gvmq/obj/KERNEL_OBJ/include/config/auto.conf:1354:CONFIG_FTRACE=y out/target/product/msmnile_gvmq/obj/KERNEL_OBJ/include/generated/autoconf.h:1356:#define CONFIG_FTRACE 1

 

console:/sys/kernel/debug/tracing # ls README events saved_cmdlines_size trace_options available_events free_buffer saved_tgids trace_pipe available_tracers instances set_event tracing_cpumask buffer_percent kprobe_events set_event_pid tracing_on buffer_size_kb kprobe_profile timestamp_mode tracing_thresh buffer_total_size_kb options trace uprobe_events current_tracer per_cpu trace_clock uprobe_profile dynamic_events printk_formats trace_marker error_log saved_cmdlines trace_marker_raw # cat available_tracers hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop

在/sys/kernel/debug/trace目录下提供了各种跟踪器和事件,一些常用的选项如下。 available_tracers:列出当前系统支持的跟踪器。 available_events:列出当前系统支持的事件。 current_tracer:设置和显示当前正在使用的跟踪器。使用echo命令可以把跟踪器的名字写入该文件, 即可以切换不同的跟踪器。默认为nop,即不做任何跟踪操作。 trace:读取跟踪信息。通过cat命令查看ftrace记录下来的跟踪信息。 tracing_on:用于开始或暂停跟踪。 trace_options:设置ftrace的一些相关选项。 ftrace 当前包含多个跟踪器,很方便用户跟踪不同类型的信息,例如进程睡眠唤醒、抢占延迟的信息。 查看 available_tracers 可以知道当前系统支持哪些跟踪器,如果系统支持的跟踪器上没有用户想要的信息, 就必须在配置内核时自行打开,然后重新编译内核。

  1. Trace 内核功能配置选项

 

android\kernel\msm-5.4\kernel\trace\Kconfig config FUNCTION_TRACER bool "Kernel Function Tracer" depends on HAVE_FUNCTION_TRACER select KALLSYMS

暂时无法在YesV2.0文档外展示此内容

直接修改kernel的配置 config 文件

arch/arm64/configs/vendor/zeekr_debug.config

 

CONFIG_TASKS_TRACE_RCU=y CONFIG_TRACEPOINTS=y CONFIG_STACKTRACE_SUPPORT=y CONFIG_TRACE_IRQFLAGS_SUPPORT=y CONFIG_TRACE_IRQFLAGS_NMI_SUPPORT=y CONFIG_HAVE_ARCH_TRACEHOOK=y CONFIG_ARCH_HAVE_TRACE_MMIO_ACCESS=y CONFIG_DMA_FENCE_TRACE=y CONFIG_STACKTRACE=y CONFIG_RCU_TRACE=y CONFIG_NOP_TRACER=y CONFIG_HAVE_FUNCTION_TRACER=y CONFIG_HAVE_DYNAMIC_FTRACE=y CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y CONFIG_HAVE_SYSCALL_TRACEPOINTS=y CONFIG_CONTEXT_SWITCH_TRACER=y CONFIG_FTRACE=y CONFIG_FUNCTION_TRACER=y CONFIG_STACK_TRACER=y CONFIG_IRQSOFF_TRACER=y CONFIG_PREEMPT_TRACER=y CONFIG_SCHED_TRACER=y CONFIG_ENABLE_DEFAULT_TRACERS=y CONFIG_FTRACE_SYSCALLS=y CONFIG_BLK_DEV_IO_TRACE=y CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y CONFIG_FUNCTION_GRAPH_TRACER=y CONFIG_DYNAMIC_FTRACE=y CONFIG_QCOM_RTB=y CONFIG_QCOM_RTB_QGKI=y CONFIG_QCOM_RTB_SEPARATE_CPUS=y

5.2 常用的ftrace跟踪器如下

nop:不跟踪任何信息。将nop写入current_tracer文件可以清空之前收集到的跟踪信息。

function:跟踪内核函数执行情况。

function_graph:可以显示类似C语言的函数调用关系图,比较直观。

wakeup:跟踪进程唤醒信息。

irqsoff:跟踪关闭中断信息,并记录关闭的最大时长。

preemptoff:跟踪关闭禁止抢占信息,并记录关闭的最大时长。

preemptirqsoff:综合了irqoff和preemptoff两个功能。

sched_switch:对内核中的进程调度活动进行跟踪。

 

常用的ftrace跟踪器如下 vim ./Documentation/trace/ftrace.rst +725 The Tracers ----------- Here is the list of current tracers that may be configured. "function" Function call tracer to trace all kernel functions. "function_graph" Similar to the function tracer except that the function tracer probes the functions on their entry whereas the function graph tracer traces on both entry and exit of the functions. It then provides the ability to draw a graph of function calls similar to C code source. "blk" The block tracer. The tracer used by the blktrace user application. "hwlat" The Hardware Latency tracer is used to detect if the hardware produces any latency. See "Hardware Latency Detector" section below. "irqsoff" Traces the areas that disable interrupts and saves the trace with the longest max latency. See tracing_max_latency. When a new max is recorded, it replaces the old trace. It is best to view this trace with the latency-format option enabled, which happens automatically when the tracer is selected. "preemptoff" Similar to irqsoff but traces and records the amount of time for which preemption is disabled. "preemptirqsoff" Similar to irqsoff and preemptoff, but traces and records the largest time for which irqs and/or preemption is disabled. "wakeup" Traces and records the max latency that it takes for the highest priority task to get scheduled after it has been woken up. Traces all tasks as an average developer would expect. "wakeup_rt" Traces and records the max latency that it takes for just RT tasks (as the current "wakeup" does). This is useful for those interested in wake up timings of RT tasks. "wakeup_dl" Traces and records the max latency that it takes for a SCHED_DEADLINE task to be woken (as the "wakeup" and "wakeup_rt" does). "mmiotrace" A special tracer that is used to trace binary module. It will trace all the calls that a module makes to the hardware. Everything it writes and reads from the I/O as well. "branch" This tracer can be configured when tracing likely/unlikely calls within the kernel. It will trace when a likely and unlikely branch is hit and if it was correct in its prediction of being correct. "nop" This is the "trace nothing" tracer. To remove all tracers from tracing simply echo "nop" into current_tracer.

Trace 调试节点 tracing # cat README

 

console:/sys/kernel/debug/tracing # cat README tracing mini-HOWTO: # echo 0 > tracing_on : quick way to disable tracing # echo 1 > tracing_on : quick way to re-enable tracing Important files: trace - The static contents of the buffer To clear the buffer write into this file: echo > trace trace_pipe - A consuming read to see the contents of the buffer current_tracer - function and latency tracers available_tracers - list of configured tracers for current_tracer error_log - error log for failed commands (that support it) buffer_size_kb - view and modify size of per cpu buffer buffer_total_size_kb - view total size of all cpu buffers trace_clock -change the clock used to order events local: Per cpu clock but may not be synced across CPUs global: Synced across CPUs but slows tracing down. counter: Not a clock, but just an increment uptime: Jiffy counter from time of boot perf: Same clock that perf events use timestamp_mode -view the mode used to timestamp events delta: Delta difference against a buffer-wide timestamp absolute: Absolute (standalone) timestamp trace_marker - Writes into this file writes into the kernel buffer trace_marker_raw - Writes into this file writes binary data into the kernel buffer tracing_cpumask - Limit which CPUs to trace instances - Make sub-buffers with: mkdir instances/foo Remove sub-buffer with rmdir trace_options - Set format or modify how tracing happens Disable an option by prefixing 'no' to the option name < print-parent nosym-offset nosym-addr trace_printk saved_cmdlines_size - echo command number in here to store comm-pid list dynamic_events - Create/append/remove/show the generic dynamic events Write into this file to define/undefine new trace events. kprobe_events - Create/append/remove/show the kernel dynamic events Write into this file to define/undefine new trace events. uprobe_events - Create/append/remove/show the userspace dynamic events Write into this file to define/undefine new trace events. accepts: event-definitions (one definition per line) Format: p[:[<group>/]<event>] <place> [<args>] r[maxactive][:[<group>/]<event>] <place> [<args>] -:[<group>/]<event> place: [<module>:]<symbol>[+<offset>]|<memaddr> place (kretprobe): [<module>:]<symbol>[+<offset>]|<memaddr> place (uprobe): <path>:<offset>[(ref_ctr_offset)] args: <name>=fetcharg[:type] fetcharg: %<register>, @<address>, @<symbol>[+|-<offset>], $stack<index>, $stack, $retval, $comm, $arg<N>, +|-[u]<offset>(<fetcharg>), \imm-value, \"imm-string" type: s8/16/32/64, u8/16/32/64, x8/16/32/64, string, symbol, b<bit-width>@<bit-offset>/<container-size>, ustring, <type>\[<array-size>\] events/ - Directory containing all trace event subsystems: enable - Write 0/1 to enable/disable tracing of all events events/<system>/ - Directory containing all trace events for <system>: enable - Write 0/1 to enable/disable tracing of all <system> events filter - If set, only events passing filter are traced events/<system>/<event>/ - Directory containing control files for <event>: enable - Write 0/1 to enable/disable tracing of <event> filter - If set, only events passing filter are traced trigger - If set, a command to perform when event is hit Format: <trigger>[:count][if <filter>] trigger: traceon, traceoff enable_event:<system>:<event> disable_event:<system>:<event> stacktrace example: echo traceoff > events/block/block_unplug/trigger echo traceoff:3 > events/block/block_unplug/trigger echo 'enable_event:kmem:kmalloc:3 if nr_rq > 1' > \ events/block/block_unplug/trigger The first disables tracing every time block_unplug is hit. The second disables tracing the first 3 times block_unplug is hit. The third enables the kmalloc event the first 3 times block_unplug is hit and has value of greater than 1 for the 'nr_rq' event field. Like function triggers, the counter is only decremented if it enabled or disabled tracing. To remove a trigger without a count: echo '!<trigger> > <system>/<event>/trigger To remove a trigger with a count: echo '!<trigger>:0 > <system>/<event>/trigger Filters can be ignored when removing a trigger. console:/sys/kernel/debug/tracing # [ 105.482003] sysrq: Show Blocked State [ 105.482461] sysrq: Show backtrace of all active CPUs

Trace 调试节点 tracing # cat trace_options

 

console:/sys/kernel/debug/tracing # cat trace_options print-parent nosym-offset nosym-addr noverbose noraw nohex nobin noblock trace_printk annotate nouserstacktrace nosym-userobj noprintk-msg-only context-info nolatency-format record-cmd norecord-tgid overwrite nodisable_on_free irq-info markers noevent-fork nostacktrace notest_nop_accept notest_nop_refuse console:/sys/kernel/debug/tracing/options # ls annotate irq-info record-cmd test_nop_refuse bin latency-format record-tgid trace_printk block markers stacktrace userstacktrace context-info overwrite sym-addr verbose disable_on_free print-parent sym-offset event-fork printk-msg-only sym-userobj hex raw test_nop_accept console:/sys/kernel/debug/tracing/options # cat irq-info 1 at verbose < 0

向current_tracer文件写入irqsoff字符串即可打开irqsoff来跟踪中断延迟。整个栗子看看

 

cd /sys/kernel/debug/tracing/ echo 0 > options/function-trace //关闭function-trace可以减少一些延迟 echo irqsoff > current_tracer echo 1 > tracing_on[...] //停顿一会儿 echo 0 > tracing_on cat trace

调试节点 tracing # cat trace

 

1|console:/sys/kernel/debug/tracing # cat trace # tracer: nop # # entries-in-buffer/entries-written: 313726/5152821 #P:8 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | <idle>-0 [001] d..2 514.850474: hrtimer_cancel: hrtimer=ffffff8ba7aa2520

调试节点 cat available_tracers

blk wakeup_dl wakeup_rt wakeup preemptirqsoff preemptoff irqsoff function nop

5.3 如何降低性能影响

使用trace_printk()可以向trace buffer中插入log,相比printk,可以输出cpuid、pid、irq/preempts状态等更多信息,且因为是per-cpu的buffer,性能影响比printk低,适用于对性能敏感的代码中。

在代码中插入tracepoint,肯定希望在不打开时,对源码性能影响最小,这里使用了动态分支(dynamic branching)技术。 struct tracepoint中的struct static_key key就是用于dynamic branching的。 kernel的dynamic_debug接口(pr_debug,dev_dbg)也是基于这个技术实现log的动态开关。 statck-keys技术详情请参考下面文档和代码。

 

Documentation/static-keys.txt include/linux/jump_label.h arch/arm64/include/asm/jump_label.h

 

常见的控制代码的执行的方法是if (enabled) 或者if unlikely(enabled)做判断; 单独的if 没有分支优化,会引入较多的branch-miss; 后一种unlikely()在enabled被设置后,也会有同样的问题。 检查的enabled往往也是全局变量,在频繁执行的代码中,也可能引入cache 同步问题,比如这变量和其它变量共享同一个cacheline时。 最好的办法就是不判断,通过动态替换指令的方式,根据enabled值先把指令替换好,消除运行时的if 判断。具体实现原理见static-keys.txt。这个功能需要gcc v4.5以上的“asm goto”的支持,同时需要体系结构提供硬件相关实现,arm64已经提供支持,代码在arch/arm64/include/asm/jump_label.h。 static-key提供的接口在include/linux/jump_label.h中

5.4 动态Trace event的实现

静态trace event需要在kernel 代码中插入trace point代码,而动态trace event是在运行时插入的。

对应tracefs里的文件是:

 

/sys/kernel/debug/tracing/kprobe_profile /sys/kernel/debug/tracing/kprobe_events /sys/kernel/debug/tracing/uprobe_profile /sys/kernel/debug/tracing/uprobe_events

从名字可以看到是基于kprobe和uprobe机制实现的。ftrace 的dynamic trace event是通过解析写入kprobe_events的命令行字符串,注册对应函数的kprobe或者kretprobe 的 handler,handler的工作就是把需要打印的内容写入trace buffer。

Kernel 调试追踪技术之Kprobe 介绍了kprobe的机制和使用方式。

uprobe的实现和kprobe类似。

kprobe是什么

kprobe 是一种动态调试机制,用于debugging,动态跟踪,性能分析,动态修改内核行为等,不需要重新编译内核代码的调试工具。probe的含义是像一个探针,可以不修改分析对象源码的情况下,获取Kernel的运行时信息。

kprobe的实现原理是把指定地址(探测点)的指令替换成一个可以让cpu进入debug模式的指令,使执行路径暂停,跳转到probe 处理函数后收集、修改信息,再跳转回来继续执行。

X86中使用的是int3指令,ARM64中使用的是BRK指令进入debug monitor模式。

这种指令替换机制使得kprobe可以在大部分kernel的代码段插入探测点,除了一些分支类的指令如brexceptioneret等。另外kprobe模块本身的代码不能probe。在pre_handlerpost_handler中可以访问、修改所有寄存器和全局变量,其变体jprobe可以检查传入参数,kretprobe可以检查返回值。且不需要修改探测目标的源码,方便用于生产系统的debug、性能分析、log记录等。

kprobe源码和接口源码 使用例子程序

 

./include/linux/kprobes.h # kprobe头文件 ./include/asm-generic/kprobes.h ./kernel/kprobes.c # kprobe核心实现 ./arch/arm64/include/asm/kprobes.h ./arch/arm64/kernel/probes/kprobes.ckprobe arm64支持 ./arch/arm64/kernel/probes/kprobes_trampoline.S ./samples/kprobes/kprobe_example.c # kprobe核心实现例子程序 ./samples/kprobes/jprobe_example.c ./samples/kprobes/kretprobe_example.c

核心实现原理 ./samples/kprobes/kprobe_example.c # kprobe核心实现例子程序

暂时无法在YesV2.0文档外展示此内容

  • 内核开发者们开发出一种不需要重新编译内核代码的调试工具:kprobe

  • kprobe 可以让用户在内核几乎所有的地址空间或函数(某些函数是被能被探测的)中插入探测点,用户可以在这些探测点上通过定义自定义函数来调试内核代码。

  • 用户可以对一个探测点进行执行前和执行后调试,在介绍 kprobe 的使用方式前,我们先来了解一下 struct kprobe 结构,其定义如下:

 

struct kprobe { struct hlist_node hlist; /* list of kprobes for multi-handler support */ struct list_head list; /*count the number of times this probe was temporarily disarmed */ unsigned long nmissed; /* location of the probe point */ kprobe_opcode_t *addr; /* Allow user to indicate symbol name of the probe point */ const char *symbol_name; /* Offset into the symbol */ unsigned int offset; /* Called before addr is executed. */ kprobe_pre_handler_t pre_handler; /* Called after addr is executed, unless... */ kprobe_post_handler_t post_handler; /* * ... called if executing addr causes a fault (eg. page fault). * Return 1 if it handled fault, otherwise kernel will see it. */ kprobe_fault_handler_t fault_handler; /* Saved opcode (which has been replaced with breakpoint) */ kprobe_opcode_t opcode; //-保存的被探测点原始指令 /* copy of the original instruction */ struct arch_specific_insn ainsn; //---被复制的被探测点的原始指令,用于单步执行,架构强相关 /* * Indicates various status flags. * Protected by kprobe_mutex after this kprobe is registered. */ u32 flags; }

  • 一个 struct kprobe 结构表示一个探测点,下面介绍一下其各个字段的作用:

  1. addr:要探测的指令所在的内存地址(由于需要知道指令的内存地址,所以比较少使用)。

  2. symbol_name:要探测的内核函数,symbol_nameaddr 只能选择一个进行探测。

  3. offset:探测点在内核函数内的偏移量,用于探测内核函数内部的指令,如果该值为0表示函数的入口。

  4. pre_handler:在探测点处的指令执行前,被调用的调试函数。

  5. post_handler:在探测点处的指令执行后,被调用的调试函数。

  6. fault_handler:在执行 pre_handlerpost_handler 或单步执行被探测指令时出现内存异常,则会调用这个回调函数。

kprobe 使用

接下来,我们介绍一下怎么使用 kprobe 来调试内核函数。目前了解这个调试起来有难度,需要反汇编能力,并对ARM的寄存器体系使用非常熟悉

  • 使用 kprobe 来进行内核调试的方式有两种:

  1. 第一种是通过编写内核模块,向内核注册探测点。探测函数可根据需要自行定制,使用灵活方便;

  2. 第二种方式是使用 kprobes on ftrace,这种方式是 kprobeftrace 结合使用,即可以通过 kprobe 来优化 ftrace 来跟踪函数的调用。

  • 由于第一种方式灵活而且功能更为强大,所以本文主要介绍第一种使用方式。

  • 要编写一个 kprobe 内核模块,可以按照以下步骤完成:

    • 第一步:根据需要来编写探测函数,如 pre_handlerpost_handler 回调函数。

    • 第二步:定义 struct kprobe 结构并且填充其各个字段,如要探测的内核函数名和各个探测回调函数。

    • 第三步:通过调用 register_kprobe 函数注册一个探测点。

    • 第四步:编写 Makefile 文件。

    • 第五步:编译并安装内核模块。

 

Kprobe config CONFIG_KPROBES=y CONFIG_KALLSYMS=y or CONFIG_KALLSYMS_ALL=y /* kprobe post_handler: called after the probed instruction is executed */ static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) #ifdef CONFIG_ARM64 pr_info("<%s> post_handler: p->addr = 0x%p, pstate = 0x%lx\n", p->symbol_name, p->addr, (long)regs->pstate); #endif

 

/*arch/arm64/include/uapi/asm/ptrace.h * User structures for general purpose, floating point and debug registers. */ struct user_pt_regs { __u64 regs[31]; __u64 sp; __u64 pc; __u64 pstate; }; /* * This struct defines the way the registers are stored on the stack during an * exception. Note that sizeof(struct pt_regs) has to be a multiple of 16 (for * stack alignment). struct user_pt_regs must form a prefix of struct pt_regs. */ struct pt_regs { union { struct user_pt_regs user_regs; struct { u64 regs[31]; u64 sp; u64 pc; u64 pstate; }; }; u64 orig_x0; #ifdef __AARCH64EB__ u32 unused2; s32 syscallno; #else s32 syscallno; u32 unused2; #endif u64 orig_addr_limit; /* Only valid when ARM64_HAS_IRQ_PRIO_MASKING is enabled. */ u64 pmr_save; u64 stackframe[2]; };

(1)、X0-X31

ARMv8有31个通用寄存器X0-X30, 还有SP、PC、XZR等寄存器

下面详细介绍写这些通用寄存器(general-purpose registers):

X0-X7 用于参数传递和返回result

X9-X15 在子函数中使用这些寄存器时,直接使用即可, 无需save/restore. 在汇编代码中x9-x15出现的频率极低

在子函数中使用这些寄存器时,直接使用即可, 无需save/restore. 在汇编代码中x9-x15出现的频率极低

X19-X29 在callee子函数中使用这些寄存器时,需要先save这些寄存器,在退出子函数时再resotre

在callee子函数中使用这些寄存器时,需要先save这些寄存器,在退出子函数时再resotre

X8, X16-X18, X29, X30 这些都是特殊用途的寄存器

– X8: 用于返回结果

– X16、X17 :进程内临时寄存器

– X18 :resrved for ABI

– X29 :FP(frame pointer register)

– X30 :LR

PSTATE (Processor state)

在aarch64中,调用ERET从一个异常返回时,会将SPSR_ELn恢复到PSTATE中,将恢复:ALU的flag、执行状态(aarch64 or aarch32)、异常级别、processor branches

PSTATE.{N, Z, C, V}可以在EL0中被修改,其余的bit只能在ELx(x>0)中修改

基于ftrace使用kprobe

kprobe和内核的ftrac结合使用,需要对内核进行配置,然后添加探测点、进行探测、查看结果。

内核kprobe配置:

 

打开"General setup"->“Kprobes”, 以及"Kernel hacking"->“Tracers”->“Enable kprobes-based dynamic events”

kprobe trace events使用

kprobe事件相关的节点有如下:

 

/sys/kernel/debug/tracing/kprobe_events-----------------------配置kprobe事件属性,增加事件之后会在kprobes下面生成对应目录。 /sys/kernel/debug/tracing/kprobe_profile----------------------kprobe事件统计属性文件。 /sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/enabled-------使能kprobe事件 /sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/filter--------过滤kprobe事件 /sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/format--------查询kprobe事件显示格式

基于ftrace使用kprobe动态trace:

 

// 添加探针 echo 'p:myprobe do_sys_open' > /sys/kernel/debug/tracing/kprobe_events echo 'r:myretprobe do_sys_open $retval' >> /sys/kernel/debug/tracing/kprobe_events echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events

kprobes的特点与使用限制:

1、kprobes允许在同一个被被探测位置注册多个 kprobe,但是目前 jprobe 却不可以;同时也不允许以其他的jprobe 回掉函数和 kprobe 的 post_handler 回调函数作为被探测点。

2、一般情况下,可以探测内核中的任何函数,包括中断处理函数。不过在 kernel/kprobes.c 和 arch/*/kernel/kprobes.c 程序中用于实现 kprobes 自身的函数是不允许被探测的,另外还有 do_page_fault 和 notifier_call_chain;

3、如果以一个内联函数为探测点,则 kprobes 可能无法保证对该函数的所有实例都注册探测点。由于gcc可能会自动将某些函数优化为内联函数,因此可能无法达到用户预期的探测效果;

4、一个探测点的回调函数可能会修改被探测函数运行的上下文,例如通过修改内核的数据结构或者保存与 struct pt_regs 结构体中的触发探测之前寄存器信息。因此 kprobes 可以被用来安装 bug 修复代码或者注入故障测试代码;

5、kprobes 会避免在处理探测点函数时再次调用另一个探测点的回调函数,例如在 printk() 函数上注册了探测点,则在它的回调函数中可能再次调用 printk 函数,此时将不再触发 printk 探测点的回调,仅仅时增加了 kprobe 结构体中 nmissed 字段的数值;

6、在 kprobes 的注册和注销过程中不会使用 mutex 锁和动态的申请内存;

7、kprobes 回调函数的运行期间是关闭内核抢占的,同时也可能在关闭中断的情况下执行,具体要视CPU架构而定。因此不论在何种情况下,在回调函数中不要调用会放弃CPU的函数(如信号量、mutex锁等);

8、kretprobe 通过替换返回地址为预定义的 trampoline 的地址来实现,因此栈回溯和gcc内嵌函数 __builtin_return_address() 调用将返回 trampoline 的地址而不是真正的被探测函数的返回地址;

9、如果一个函数的调用此处和返回次数不相等,则在类似这样的函数上注册 kretprobe 将可能不会达到预期的效果,例如 do_exit() 函数会存在问题,而 do_execve() 函数和 do_fork() 函数不会;

10、如果当在进入和退出一个函数时,CPU运行在非当前任务所有的栈上,那么往该函数上注册 kretprobe 可能会导致不可预料的后果,因此,kprobes 不支持在 X86_64 的结构下为 __switch_to() 函数注册 kretprobe,将直接返回-EINVAL。

eBPF

eBPF (Extended Berkeley Packet Filter)是最近比较流行的内核探测技术,通过在Kernel中运行一个虚拟机,执行用户传入的字节码,实现对kernel内部信息的探测能力,比较安全易用。

其动态探测部分是基于kprobe和kretprobe实现的,可以实现对任意函数的探测、数据处理等能力。

  1. atrace 机制 收集服务和应用中的用户空间
 

msmnile_gvmq:/ # atrace -h atrace: invalid option -- h usage: atrace [options] [categories...] options include: -a appname enable app-level tracing for a comma separated list of cmdlines; * is a wildcard matching any process -b N use a trace buffer size of N KB -c trace into a circular buffer -f filename use the categories written in a file as space-separated values in a line -k fname,... trace the listed kernel functions -n ignore signals -s N sleep for N seconds before tracing [default 0] -t N trace for N seconds [default 5] -z compress the trace dump --async_start start circular trace and return immediately --async_dump dump the current contents of circular trace buffer --async_stop stop tracing and dump the current contents of circular trace buffer --stream stream trace to stdout as it enters the trace buffer Note: this can take significant CPU time, and is best used for measuring things that are not affected by CPU performance, like pagecache usage. --list_categories list the available tracing categories -o filename write the trace to the specified file instead of stdout.

1.1 直接抓:

 

adb shell atrace --async_start -b 30720 gfx input view webview wm am sm audio video binder_lock binder_driver camera hal res dalvik rs bionic power pm ss database network adb vibrator aidl sched

1.2 抓app自定义:

adb shell atrace --async_start -b 30720 gfx input view webview wm am sm audio video binder_lock binder_driver camera hal res dalvik rs bionic power pm ss database network adb vibrator aidl sched -a xxx.xxx.xxx

停止:

adb shell atrace --async_stop -z -c -o /data/local/tmp/y_trace

adb pull /data/local/tmp/y_trace /home/yang/code

  1. 浏览 Systrace 报告

绿色:正在运行线程正在完成与某个进程相关的工作或正在响应中断。

蓝色:可运行线程可以运行但目前未进行调度。

白色:休眠线程没有可执行的任务,可能是因为线程在遇到互斥锁定时被阻止。

橙色:不可中断的休眠线程在遇到 I/O 操作时被阻止或正在等待磁盘操作完成。

紫色:可中断的休眠线程在遇到另一项内核操作(通常是内存管理)时被阻止

线程状态主要有下面几个

1 绿色 : 运行中(Running)

只有在该状态的线程才可能在 cpu 上运行。而同一时刻可能有多个线程处于可执行状态,这些线程的 task_struct 结构被放入对应 cpu 的可执行队列中(一个线程最多只能出现在一个 cpu 的可执行队列中)。调度器的任务就是从各个 cpu 的可执行队列中分别选择一个线程在该cpu 上运行

作用:我们经常会查看 Running 状态的线程,查看其运行的时间,与竞品做对比,分析快或者慢的原因:

是否频率不够?

是否跑在了小核上?

是否频繁在 Running 和 Runnable 之间切换?为什么?

是否频繁在 Running 和 Sleep 之间切换?为什么?

是否跑在了不该跑的核上面?比如不重要的线程占用了超大核

2 蓝色 : 可运行(Runnable)

线程可以运行但当前没有安排,在等待 cpu 调度

作用:Runnable 状态的线程状态持续时间越长,则表示 cpu 的调度越忙,没有及时处理到这个任务:

是否后台有太多的任务在跑?

没有及时处理是因为频率太低?

没有及时处理是因为被限制到某个 cpuset 里面,但是 cpu 很满?

此时 Running 的任务是什么?为什么?

3 白色 : 休眠中(Sleep)

线程没有工作要做,可能是因为线程在互斥锁上被阻塞,也可能等待某个线程返回,可以看是被谁唤醒,去查看是等待哪个线程。

作用 : 这里一般是在等事件驱动

4 橘色 : 不可中断的睡眠态 (Uninterruptible Sleep - IO Block)

线程在I / O上被阻塞或等待磁盘操作完成,一般底线都会标识出此时的 callsite :wait_on_page_locked_killable

作用:这个一般是标示 io 操作慢,如果有大量的橘色不可中断的睡眠态出现,那么一般是由于进入了低内存状态,申请内存的时候触发 pageFault, linux 系统的 page cache 链表中有时会出现一些还没准备好的 page(即还没把磁盘中的内容完全地读出来) , 而正好此时用户在访问这个 page 时就会出现 wait_on_page_locked_killable 阻塞了. 只有系统当 io 操作很繁忙时, 每笔的 io 操作都需要等待排队时, 极其容易出现且阻塞的时间往往会比较长.

5 棕色 : 不可中断的睡眠态( Uninterruptible Sleep (non-IO))

线程在另一个内核操作(通常是内存管理)上被阻塞。

作用:一般是陷入了内核态,有些情况下是正常的,有些情况下是不正常的,需要按照具体的情况去分析

5、任务唤醒信息分析

Perfetto 会标识出一个非常有用的信息,可以帮助我们进行线程调用等待相关的分析。

一个线程被唤醒的信息往往比较重要,知道他被谁唤醒,那么我们也就知道了他们之间的调用等待关系,如果一个线程出现一段比较长的 sleep 情况,然后被唤醒,那么我们就可以去看是谁唤醒了这个线程,对应的就可以查看唤醒者的信息,看看为什么唤醒者这么晚才唤醒。

一个常见的情况是:应用主线程程使用 Binder 与 SystemServer 的 AMS 进行通信,但是恰好 AMS 的这个函数正在等待锁释放(或者这个函数本身执行时间很长),那么应用主线程就需要等待比较长的时间,那么就会出现性能问题,比如响应慢或者卡顿,这就是为什么后台有大量的进程在运行,或者跑完 Monkey 之后,整机性能会下降的一个主要原因

另外一个场景的情况是:应用主线程在等待此应用的其他线程执行的结果,这时候线程唤醒信息就可以用来分析主线程到底被哪个线程 Block 住了

6、信息区数据解析

6.1 CPU架构

简单来说目前的手机 CPU 按照核心数和架构来说,可以分为下面三类:

非大小核架构(正常所以核一样)

大小核架构(正常0-3小核,4-7大核)

大中小核架构(正常0-3小核,4-6中核,7超大核)

6.2 CPU Info信息

Pefetto 中CPU Info信息一般在最上面,里面经常会用到的信息包括:

CPU 频率变化情况

任务执行情况

大小核的调度情况

CPU Boost 调度情况

总的来说,Systrace 中的 Kernel CPU Info 这里一般是看任务调度信息,查看是否是频率或者调度导致当前任务出现性能问题,举例如下:

某个场景的任务执行比较慢,我们就可以查看是不是这个任务被调度到了小核?

某个场景的任务执行比较慢,当前执行这个任务的 CPU 频率是不是不够?

我的任务比较特殊,能不能把我这个任务放到大核去跑?

我这个场景对 CPU 要求很高,我能不能要求在我这个场景运行的时候,限制 CPU 最低频率?

参考文档:

https://developer.android.google.cn/tools/perfetto?hl=zh-cn

https://blog.csdn.net/weixin_43700867/article/details/140072700

https://blog.csdn.net/weixin_45264425/article/details/125964725

https://www.cnblogs.com/hpyu/p/14348523.html

https://blog.csdn.net/yangguannan/article/details/140214336

https://developer.android.google.cn/topic/performance/tracing/navigate-report?hl=nl&skip_cache=true

https://www.cnblogs.com/hpyu/p/14257305.html Kernel 调试追踪技术之Kprobe

https://zhuanlan.zhihu.com/p/483206899

https://blog.csdn.net/weixin_45030965/article/details/125911279

https://blog.csdn.net/weixin_42135087/article/details/111263720 ARMV8-aarch64的通用寄存器

https://www.jianshu.com/p/37d09051177e

https://blog.csdn.net/chaoguo1234/article/details/139933630

https://blog.csdn.net/chaoguo1234/article/details/139933633

https://m.elecfans.com/article/2195973.html

https://blog.csdn.net/mrpre/article/details/106801888

https://www.cnblogs.com/dengchj/p/14919393.html

https://blog.csdn.net/kangbin825/article/details/139066711

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 容联云容犀Copilot&Agent入选《中国 AI Agent 产品罗盘》
  • 【鸿蒙】HarmonyOS NEXT星河入门到实战1-开发环境准备
  • C++中的for-each循环
  • 设计模式】Listener模式和Visitor模式的区别
  • Android 设计模式
  • python画图|3D垂线标记
  • HT5169 内置BOOST升压的11WI2S输入D类音频功放
  • wangeditor——cdn引入的形式创建一个简易版编辑器——js技能提升
  • 从状态管理到性能优化:全面解析 Android Compose
  • 【 前端优化】Vue 3 性能优化技巧
  • 二叉树(上)
  • 2.大语言模型LLM的涌现能力和关键技术
  • JVM面试(七)G1垃圾收集器剖析
  • css问题:display:flex布局+justify-content: space-between; 最后一行不能左对齐
  • 2024年重磅报告!国内AI大模型产业飞速发展!
  • 【笔记】你不知道的JS读书笔记——Promise
  • Angular6错误 Service: No provider for Renderer2
  • canvas 高仿 Apple Watch 表盘
  • canvas 五子棋游戏
  • Elasticsearch 参考指南(升级前重新索引)
  • ES6--对象的扩展
  • Github访问慢解决办法
  • HTTP请求重发
  • JavaScript 奇技淫巧
  • Java反射-动态类加载和重新加载
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • nodejs实现webservice问题总结
  • Octave 入门
  • React Native移动开发实战-3-实现页面间的数据传递
  • React-Native - 收藏集 - 掘金
  • SpiderData 2019年2月23日 DApp数据排行榜
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 从零搭建Koa2 Server
  • 如何打造100亿SDK累计覆盖量的大数据系统
  • 微服务框架lagom
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • C# - 为值类型重定义相等性
  • raise 与 raise ... from 的区别
  • 数据库巡检项
  • 新海诚画集[秒速5センチメートル:樱花抄·春]
  • # centos7下FFmpeg环境部署记录
  • (delphi11最新学习资料) Object Pascal 学习笔记---第13章第6节 (嵌套的Finally代码块)
  • (PADS学习)第二章:原理图绘制 第一部分
  • (STM32笔记)九、RCC时钟树与时钟 第一部分
  • (第30天)二叉树阶段总结
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • .jks文件(JAVA KeyStore)
  • .NET : 在VS2008中计算代码度量值
  • .net core开源商城系统源码,支持可视化布局小程序
  • .NET/C# 使窗口永不激活(No Activate 永不获得焦点)
  • .net6Api后台+uniapp导出Excel
  • .netcore 获取appsettings
  • .NET企业级应用架构设计系列之结尾篇
  • :=