/* * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved. * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "los_perf_pri.h" #include "perf_pmu_pri.h" #include "perf_output_pri.h" #include "los_init.h" #include "los_process.h" #include "los_tick.h" #include "los_sys.h" #include "los_spinlock.h" STATIC Pmu *g_pmu = NULL; STATIC PerfCB g_perfCb = {0}; LITE_OS_SEC_BSS SPIN_LOCK_INIT(g_perfSpin); #define PERF_LOCK(state) LOS_SpinLockSave(&g_perfSpin, &(state)) #define PERF_UNLOCK(state) LOS_SpinUnlockRestore(&g_perfSpin, (state)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) STATIC INLINE UINT64 OsPerfGetCurrTime(VOID) { #ifdef LOSCFG_PERF_CALC_TIME_BY_TICK return LOS_TickCountGet(); #else return HalClockGetCycles(); #endif } STATIC UINT32 OsPmuInit(VOID) { #ifdef LOSCFG_PERF_HW_PMU if (OsHwPmuInit() != LOS_OK) { return LOS_ERRNO_PERF_HW_INIT_ERROR; } #endif #ifdef LOSCFG_PERF_TIMED_PMU if (OsTimedPmuInit() != LOS_OK) { return LOS_ERRNO_PERF_TIMED_INIT_ERROR; } #endif #ifdef LOSCFG_PERF_SW_PMU if (OsSwPmuInit() != LOS_OK) { return LOS_ERRNO_PERF_SW_INIT_ERROR; } #endif return LOS_OK; } STATIC UINT32 OsPerfConfig(PerfEventConfig *eventsCfg) { UINT32 i; UINT32 ret; g_pmu = OsPerfPmuGet(eventsCfg->type); if (g_pmu == NULL) { PRINT_ERR("perf config type error %u!\n", eventsCfg->type); return LOS_ERRNO_PERF_INVALID_PMU; } UINT32 eventNum = MIN(eventsCfg->eventsNr, PERF_MAX_EVENT); (VOID)memset_s(&g_pmu->events, sizeof(PerfEvent), 0, sizeof(PerfEvent)); for (i = 0; i < eventNum; i++) { g_pmu->events.per[i].eventId = eventsCfg->events[i].eventId; g_pmu->events.per[i].period = eventsCfg->events[i].period; } g_pmu->events.nr = i; g_pmu->events.cntDivided = eventsCfg->predivided; g_pmu->type = eventsCfg->type; ret = g_pmu->config(); if (ret != LOS_OK) { PRINT_ERR("perf config failed!\n"); (VOID)memset_s(&g_pmu->events, sizeof(PerfEvent), 0, sizeof(PerfEvent)); return LOS_ERRNO_PERF_PMU_CONFIG_ERROR; } return LOS_OK; } STATIC VOID OsPerfPrintCount(VOID) { UINT32 index; UINT32 intSave; UINT32 cpuid = ArchCurrCpuid(); PerfEvent *events = &g_pmu->events; UINT32 eventNum = events->nr; PERF_LOCK(intSave); for (index = 0; index < eventNum; index++) { Event *event = &(events->per[index]); /* filter out event counter with no event binded. */ if (event->period == 0) { continue; } PRINT_EMG("[%s] eventType: 0x%x [core %u]: %llu\n", g_pmu->getName(event), event->eventId, cpuid, event->count[cpuid]); } PERF_UNLOCK(intSave); } STATIC INLINE VOID OsPerfPrintTs(VOID) { #ifdef LOSCFG_PERF_CALC_TIME_BY_TICK DOUBLE time = (g_perfCb.endTime - g_perfCb.startTime) * 1.0 / LOSCFG_BASE_CORE_TICK_PER_SECOND; #else DOUBLE time = (g_perfCb.endTime - g_perfCb.startTime) * 1.0 / OS_SYS_CLOCK; #endif PRINT_EMG("time used: %.6f(s)\n", time); } STATIC VOID OsPerfStart(VOID) { UINT32 cpuid = ArchCurrCpuid(); if (g_pmu == NULL) { PRINT_ERR("pmu not registered!\n"); return; } if (g_perfCb.pmuStatusPerCpu[cpuid] != PERF_PMU_STARTED) { UINT32 ret = g_pmu->start(); if (ret != LOS_OK) { PRINT_ERR("perf start on core:%u failed, ret = 0x%x\n", cpuid, ret); return; } g_perfCb.pmuStatusPerCpu[cpuid] = PERF_PMU_STARTED; } else { PRINT_ERR("percpu status err %d\n", g_perfCb.pmuStatusPerCpu[cpuid]); } } STATIC VOID OsPerfStop(VOID) { UINT32 cpuid = ArchCurrCpuid(); if (g_pmu == NULL) { PRINT_ERR("pmu not registered!\n"); return; } if (g_perfCb.pmuStatusPerCpu[cpuid] != PERF_PMU_STOPED) { UINT32 ret = g_pmu->stop(); if (ret != LOS_OK) { PRINT_ERR("perf stop on core:%u failed, ret = 0x%x\n", cpuid, ret); return; } if (!g_perfCb.needSample) { OsPerfPrintCount(); } g_perfCb.pmuStatusPerCpu[cpuid] = PERF_PMU_STOPED; } else { PRINT_ERR("percpu status err %d\n", g_perfCb.pmuStatusPerCpu[cpuid]); } } STATIC INLINE UINT32 OsPerfSaveIpInfo(CHAR *buf, IpInfo *info) { UINT32 size = 0; #ifdef LOSCFG_KERNEL_VM UINT32 len = ALIGN(info->len, sizeof(size_t)); *(UINTPTR *)buf = info->ip; /* save ip */ size += sizeof(UINTPTR); *(UINT32 *)(buf + size) = len; /* save f_path length */ size += sizeof(UINT32); if (strncpy_s(buf + size, REGION_PATH_MAX, info->f_path, info->len) != EOK) { /* save f_path */ PRINT_ERR("copy f_path failed, %s\n", info->f_path); } size += len; #else *(UINTPTR *)buf = info->ip; /* save ip */ size += sizeof(UINTPTR); #endif return size; } STATIC UINT32 OsPerfBackTrace(PerfBackTrace *callChain, UINT32 maxDepth, PerfRegs *regs) { UINT32 count = BackTraceGet(regs->fp, (IpInfo *)(callChain->ip), maxDepth); PRINT_DEBUG("backtrace depth = %u, fp = 0x%x\n", count, regs->fp); return count; } STATIC INLINE UINT32 OsPerfSaveBackTrace(CHAR *buf, PerfBackTrace *callChain, UINT32 count) { UINT32 i; *(UINT32 *)buf = count; UINT32 size = sizeof(UINT32); for (i = 0; i < count; i++) { size += OsPerfSaveIpInfo(buf + size, &(callChain->ip[i])); } return size; } STATIC UINT32 OsPerfCollectData(Event *event, PerfSampleData *data, PerfRegs *regs) { UINT32 size = 0; UINT32 depth; IpInfo pc = {0}; PerfBackTrace callChain = {0}; UINT32 sampleType = g_perfCb.sampleType; CHAR *p = (CHAR *)data; if (sampleType & PERF_RECORD_CPU) { *(UINT32 *)(p + size) = ArchCurrCpuid(); size += sizeof(data->cpuid); } if (sampleType & PERF_RECORD_TID) { *(UINT32 *)(p + size) = LOS_CurTaskIDGet(); size += sizeof(data->taskId); } if (sampleType & PERF_RECORD_PID) { *(UINT32 *)(p + size) = LOS_GetCurrProcessID(); size += sizeof(data->processId); } if (sampleType & PERF_RECORD_TYPE) { *(UINT32 *)(p + size) = event->eventId; size += sizeof(data->eventId); } if (sampleType & PERF_RECORD_PERIOD) { *(UINT32 *)(p + size) = event->period; size += sizeof(data->period); } if (sampleType & PERF_RECORD_TIMESTAMP) { *(UINT64 *)(p + size) = OsPerfGetCurrTime(); size += sizeof(data->time); } if (sampleType & PERF_RECORD_IP) { OsGetUsrIpInfo(regs->pc, &pc); size += OsPerfSaveIpInfo(p + size, &pc); } if (sampleType & PERF_RECORD_CALLCHAIN) { depth = OsPerfBackTrace(&callChain, PERF_MAX_CALLCHAIN_DEPTH, regs); size += OsPerfSaveBackTrace(p + size, &callChain, depth); } return size; } /* * return TRUE if the taskId in the task filter list, return FALSE otherwise; * return TRUE if user haven't specified any taskId(which is supposed * to instrument the whole system) */ STATIC INLINE BOOL OsFilterId(UINT32 id, const UINT32 *ids, UINT8 idsNr) { UINT32 i; if (!idsNr) { return TRUE; } for (i = 0; i < idsNr; i++) { if (ids[i] == id) { return TRUE; } } return FALSE; } STATIC INLINE BOOL OsPerfFilter(UINT32 taskId, UINT32 processId) { return OsFilterId(taskId, g_perfCb.taskIds, g_perfCb.taskIdsNr) && OsFilterId(processId, g_perfCb.processIds, g_perfCb.processIdsNr); } STATIC INLINE UINT32 OsPerfParamValid(VOID) { UINT32 index; UINT32 res = 0; if (g_pmu == NULL) { return 0; } PerfEvent *events = &g_pmu->events; UINT32 eventNum = events->nr; for (index = 0; index < eventNum; index++) { res |= events->per[index].period; } return res; } STATIC UINT32 OsPerfHdrInit(UINT32 id) { PerfDataHdr head = { .magic = PERF_DATA_MAGIC_WORD, .sampleType = g_perfCb.sampleType, .sectionId = id, .eventType = g_pmu->type, .len = sizeof(PerfDataHdr), }; return OsPerfOutputWrite((CHAR *)&head, head.len); } VOID OsPerfUpdateEventCount(Event *event, UINT32 value) { if (event == NULL) { return; } event->count[ArchCurrCpuid()] += (value & 0xFFFFFFFF); /* event->count is UINT64 */ } VOID OsPerfHandleOverFlow(Event *event, PerfRegs *regs) { PerfSampleData data; UINT32 len; (VOID)memset_s(&data, sizeof(PerfSampleData), 0, sizeof(PerfSampleData)); if ((g_perfCb.needSample) && OsPerfFilter(LOS_CurTaskIDGet(), LOS_GetCurrProcessID())) { len = OsPerfCollectData(event, &data, regs); OsPerfOutputWrite((CHAR *)&data, len); } } STATIC UINT32 OsPerfInit(VOID) { UINT32 ret; if (g_perfCb.status != PERF_UNINIT) { ret = LOS_ERRNO_PERF_STATUS_INVALID; goto PERF_INIT_ERROR; } ret = OsPmuInit(); if (ret != LOS_OK) { goto PERF_INIT_ERROR; } ret = OsPerfOutputInit(NULL, LOSCFG_PERF_BUFFER_SIZE); if (ret != LOS_OK) { ret = LOS_ERRNO_PERF_BUF_ERROR; goto PERF_INIT_ERROR; } g_perfCb.status = PERF_STOPPED; PERF_INIT_ERROR: return ret; } STATIC VOID PerfInfoDump(VOID) { UINT32 i; if (g_pmu != NULL) { PRINTK("type: %d\n", g_pmu->type); for (i = 0; i < g_pmu->events.nr; i++) { PRINTK("events[%d]: %d, 0x%x\n", i, g_pmu->events.per[i].eventId, g_pmu->events.per[i].period); } PRINTK("predivided: %d\n", g_pmu->events.cntDivided); } else { PRINTK("pmu is NULL\n"); } PRINTK("sampleType: 0x%x\n", g_perfCb.sampleType); for (i = 0; i < g_perfCb.taskIdsNr; i++) { PRINTK("filter taskIds[%d]: %d\n", i, g_perfCb.taskIds[i]); } for (i = 0; i < g_perfCb.processIdsNr; i++) { PRINTK("filter processIds[%d]: %d\n", i, g_perfCb.processIds[i]); } PRINTK("needSample: %d\n", g_perfCb.needSample); } STATIC INLINE VOID OsPerfSetFilterIds(UINT32 *dstIds, UINT8 *dstIdsNr, UINT32 *ids, UINT32 idsNr) { errno_t ret; if (idsNr) { ret = memcpy_s(dstIds, PERF_MAX_FILTER_TSKS * sizeof(UINT32), ids, idsNr * sizeof(UINT32)); if (ret != EOK) { PRINT_ERR("In %s At line:%d execute memcpy_s error\n", __FUNCTION__, __LINE__); *dstIdsNr = 0; return; } *dstIdsNr = MIN(idsNr, PERF_MAX_FILTER_TSKS); } else { *dstIdsNr = 0; } } UINT32 LOS_PerfConfig(PerfConfigAttr *attr) { UINT32 ret; UINT32 intSave; if (attr == NULL) { return LOS_ERRNO_PERF_CONFIG_NULL; } PERF_LOCK(intSave); if (g_perfCb.status != PERF_STOPPED) { ret = LOS_ERRNO_PERF_STATUS_INVALID; PRINT_ERR("perf config status error : 0x%x\n", g_perfCb.status); goto PERF_CONFIG_ERROR; } g_pmu = NULL; g_perfCb.needSample = attr->needSample; g_perfCb.sampleType = attr->sampleType; OsPerfSetFilterIds(g_perfCb.taskIds, &g_perfCb.taskIdsNr, attr->taskIds, attr->taskIdsNr); OsPerfSetFilterIds(g_perfCb.processIds, &g_perfCb.processIdsNr, attr->processIds, attr->processIdsNr); ret = OsPerfConfig(&attr->eventsCfg); PerfInfoDump(); PERF_CONFIG_ERROR: PERF_UNLOCK(intSave); return ret; } VOID LOS_PerfStart(UINT32 sectionId) { UINT32 intSave; UINT32 ret; PERF_LOCK(intSave); if (g_perfCb.status != PERF_STOPPED) { PRINT_ERR("perf start status error : 0x%x\n", g_perfCb.status); goto PERF_START_ERROR; } if (!OsPerfParamValid()) { PRINT_ERR("forgot call `LOS_PerfConfig(...)` before perf start?\n"); goto PERF_START_ERROR; } if (g_perfCb.needSample) { ret = OsPerfHdrInit(sectionId); /* section header init */ if (ret != LOS_OK) { PRINT_ERR("perf hdr init error 0x%x\n", ret); goto PERF_START_ERROR; } } SMP_CALL_PERF_FUNC(OsPerfStart); /* send to all cpu to start pmu */ g_perfCb.status = PERF_STARTED; g_perfCb.startTime = OsPerfGetCurrTime(); PERF_START_ERROR: PERF_UNLOCK(intSave); return; } VOID LOS_PerfStop(VOID) { UINT32 intSave; PERF_LOCK(intSave); if (g_perfCb.status != PERF_STARTED) { PRINT_ERR("perf stop status error : 0x%x\n", g_perfCb.status); goto PERF_STOP_ERROR; } SMP_CALL_PERF_FUNC(OsPerfStop); /* send to all cpu to stop pmu */ OsPerfOutputFlush(); if (g_perfCb.needSample) { OsPerfOutputInfo(); } g_perfCb.status = PERF_STOPPED; g_perfCb.endTime = OsPerfGetCurrTime(); OsPerfPrintTs(); PERF_STOP_ERROR: PERF_UNLOCK(intSave); return; } UINT32 LOS_PerfDataRead(CHAR *dest, UINT32 size) { return OsPerfOutputRead(dest, size); } VOID LOS_PerfNotifyHookReg(const PERF_BUF_NOTIFY_HOOK func) { UINT32 intSave; PERF_LOCK(intSave); OsPerfNotifyHookReg(func); PERF_UNLOCK(intSave); } VOID LOS_PerfFlushHookReg(const PERF_BUF_FLUSH_HOOK func) { UINT32 intSave; PERF_LOCK(intSave); OsPerfFlushHookReg(func); PERF_UNLOCK(intSave); } VOID OsPerfSetIrqRegs(UINTPTR pc, UINTPTR fp) { LosTaskCB *runTask = (LosTaskCB *)ArchCurrTaskGet(); runTask->pc = pc; runTask->fp = fp; } LOS_MODULE_INIT(OsPerfInit, LOS_INIT_LEVEL_KMOD_EXTENDED);