blob: 0f0b8bdbc07baeabf68ff6d3394d5caf6048ae85 [file]
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <string>
#include <android-base/expected.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include "SPERecorder.h"
#include "environment.h"
#include "utils.h"
namespace simpleperf {
static const std::string DEVICES_DIR = "/sys/bus/event_source/devices/";
static const uint64_t ARM_SPE_HEADER_VERSION = 1;
SPERecorder& SPERecorder::GetInstance() {
static SPERecorder spe;
return spe;
}
void SPERecorder::ReadSpeMidrInfo(const std::vector<int>& cpus) {
std::vector<int> online_cpus = GetOnlineCpus();
for (auto cpu : online_cpus) {
uint64_t midr_val = 0;
std::string s;
bool spe_enabled = std::find(cpus.begin(), cpus.end(), cpu) != cpus.end();
const std::string midr_path = android::base::StringPrintf(
"/sys/devices/system/cpu/cpu%d/regs/identification/midr_el1", cpu);
if (!android::base::ReadFileToString(midr_path, &s) ||
!android::base::ParseUint(android::base::Trim(s), &midr_val)) {
LOG(ERROR) << "SPE: CPU " << cpu << " MIDR path file read error; file path: " << midr_path;
continue;
}
spe_midr_info_.push_back({cpu, spe_enabled, midr_val});
}
}
std::string SPERecorder::ParseSpeTypes(const std::string& name) {
size_t slash_pos = name.find('/');
if (slash_pos == std::string::npos) {
if (name == kSPEEventName) {
return kSPEDefaultDeviceName;
}
return name;
}
std::string spe_device = name.substr(0, slash_pos);
std::string config_string = name.substr(slash_pos + 1);
size_t slash_pos2 = config_string.find('/');
if ((slash_pos2 == std::string::npos) || (slash_pos2 != (config_string.size() - 1))) {
LOG(ERROR) << "Invalid SPE input format, config parameters should be closed with a \"/\"";
return name;
}
config_string.pop_back(); // remove '/' from the end
std::stringstream config_stream(config_string);
std::string config;
while (std::getline(config_stream, config, ',')) {
if (config.size() == 0) {
continue;
}
size_t eq_pos = config.find('=');
if (eq_pos == std::string::npos) {
LOG(ERROR) << "Invalid config, missing '=<value>' : " << config;
return name;
}
std::string config_name = config.substr(0, eq_pos);
std::string value_as_str = config.substr(eq_pos + 1);
uint64_t val = 0;
if (android::base::ParseUint(android::base::Trim(value_as_str), &val)) {
spe_config_.insert({config_name, val});
} else {
LOG(ERROR) << "Invalid config value, config: " << config << " value: " << value_as_str;
}
}
if (spe_device == kSPEEventName) {
spe_device = kSPEDefaultDeviceName;
}
return spe_device;
}
AuxTraceInfoRecord SPERecorder::CreateAuxTraceInfoRecord() {
AuxTraceInfoRecord::DataType data;
memset(&data, 0, sizeof(data));
data.aux_type = AuxTraceInfoRecord::AUX_TYPE_SPE;
data.version = ARM_SPE_HEADER_VERSION;
data.nr_cpu = spe_midr_info_.size();
data.pmu_type = GetSPEEventType();
std::vector<AuxTraceInfoRecord::SPEInfo> spe(spe_midr_info_.size());
size_t pos = 0;
uint64_t cap_min_ival = GetMinInterval();
for (auto& p : spe_midr_info_) {
auto& e = spe[pos++];
e.magic = AuxTraceInfoRecord::MAGIC_SPE;
e.cpu = p.cpu;
e.cpu_midr = p.midr_val;
if (p.spe_enabled) {
e.enabled = 1;
e.cap_min_ival = cap_min_ival;
} else {
e.enabled = 0;
e.cap_min_ival = 0;
}
}
return AuxTraceInfoRecord(data, spe);
}
int SPERecorder::GetSPEEventType() {
if (event_type_ == 0) {
std::string s;
std::string path = DEVICES_DIR + kSPEDefaultDeviceName + "/type";
if (IsDir(path) || !android::base::ReadFileToString(path, &s) ||
!android::base::ParseInt(android::base::Trim(s), &event_type_)) {
event_type_ = -1;
}
}
return event_type_;
}
uint64_t SPERecorder::GetMinInterval() {
uint64_t cap_min_ival = 0;
std::string min_interval;
if (!android::base::ReadFileToString(DEVICES_DIR + kSPEDefaultDeviceName + "/caps/min_interval",
&min_interval) ||
!android::base::ParseUint(android::base::Trim(min_interval), &cap_min_ival)) {
LOG(ERROR) << "Failed to read min interval from sysfs on path " << DEVICES_DIR
<< kSPEDefaultDeviceName << "/caps/min_interval";
}
return cap_min_ival;
}
bool SPERecorder::FindSpeConfig(const std::string& name, uint64_t* ret_val) {
auto it = spe_config_.find(name);
if (it != spe_config_.end()) {
*ret_val = it->second;
return true;
} else {
return false;
}
}
} // namespace simpleperf