/* Copyright (c) 2018-2025, Arm Limited and Contributors * Copyright (c) 2020-2025, Broadcom Inc. * * SPDX-License-Identifier: Apache-2.0 * * 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 "hwcpipe_stats_provider.h" namespace { const char *get_product_family_name(hwcpipe::device::gpu_family f) { using gpu_family = hwcpipe::device::gpu_family; switch (f) { case gpu_family::bifrost: return "Bifrost"; case gpu_family::midgard: return "Midgard"; case gpu_family::valhall: return "Valhall"; default: return "Unknown"; } } } // namespace namespace vkb { HWCPipeStatsProvider::HWCPipeStatsProvider(std::set &requested_stats) { // Mapping of stats to their hwcpipe availability // clang-format off StatDataMap hwcpipe_stats = { {StatIndex::gpu_cycles, {hwcpipe_counter::MaliGPUActiveCy}}, {StatIndex::gpu_vertex_cycles, {hwcpipe_counter::MaliNonFragQueueActiveCy, {MaliNonFragActiveCy, MaliBinningQueueActiveCy} }}, {StatIndex::gpu_load_store_cycles, {hwcpipe_counter::MaliLSIssueCy}}, {StatIndex::gpu_tiles, {hwcpipe_counter::MaliFragTile}}, {StatIndex::gpu_killed_tiles, {hwcpipe_counter::MaliFragTileKill}}, {StatIndex::gpu_fragment_cycles, {hwcpipe_counter::MaliFragQueueActiveCy, {MaliFragActiveCy, MaliMainQueueActiveCy}}}, {StatIndex::gpu_fragment_jobs, {hwcpipe_counter::MaliFragQueueJob, {MaliMainQueueJob}}}, {StatIndex::gpu_ext_reads, {hwcpipe_counter::MaliExtBusRdBt}}, {StatIndex::gpu_ext_writes, {hwcpipe_counter::MaliExtBusWrBt}}, {StatIndex::gpu_ext_read_stalls, {hwcpipe_counter::MaliExtBusRdStallCy}}, {StatIndex::gpu_ext_write_stalls, {hwcpipe_counter::MaliExtBusWrStallCy}}, {StatIndex::gpu_ext_read_bytes, {hwcpipe_counter::MaliExtBusRdBy}}, {StatIndex::gpu_ext_write_bytes, {hwcpipe_counter::MaliExtBusWrBy}}, {StatIndex::gpu_tex_cycles, {hwcpipe_counter::MaliTexIssueCy}}}; // clang-format on // Detect all GPUs & print some info for (const auto &gpu : hwcpipe::find_gpus()) { LOGI("HWCPipe: ------------------------------------------------------------"); LOGI("HWCPipe: GPU Device {}:", gpu.get_device_number()); LOGI("HWCPipe: ------------------------------------------------------------"); LOGI("HWCPipe: Product Family: {}", get_product_family_name(gpu.get_gpu_family())); LOGI("HWCPipe: Number of Cores: {}", gpu.num_shader_cores()); LOGI("HWCPipe: Bus Width: {}", gpu.bus_width()); } // Probe device 0 (i.e. /dev/mali0) auto gpu = hwcpipe::gpu(0); if (!gpu) { LOGE("HWCPipe: Mali GPU device 0 is missing"); } auto config = hwcpipe::sampler_config(gpu); auto counter_db = hwcpipe::counter_database{}; std::error_code ec; for (const auto &stat : requested_stats) { auto it = hwcpipe_stats.find(stat); if (it != hwcpipe_stats.end()) { hwcpipe::counter_metadata meta; auto ec = counter_db.describe_counter(it->second.counter, meta); if (ec) { LOGE("HWCPipe: unknown counter"); } ec = config.add_counter(it->second.counter); if (ec) { // Some counters have changed in recent devices auto &fallback_list = it->second.fallback_list; if (!fallback_list.empty()) { for (const auto &fallback : fallback_list) { ec = counter_db.describe_counter(fallback, meta); ec = config.add_counter(fallback); if (!ec) { // Replace counter with available alternative it->second.counter = fallback; break; } } } } if (ec) { LOGE("HWCPipe: '{}' counter not supported by this GPU.", meta.name); } else { stat_data[stat] = hwcpipe_stats[stat]; LOGI("HWCPipe: enabled '{}' counter", meta.name); } } } // Remove any supported stats from the requested set. // Subsequent providers will then only look for things that aren't already supported. for (const auto &iter : stat_data) { requested_stats.erase(iter.first); } stat_data_count = stat_data.size(); sampler = std::make_unique>(config); if (stat_data_count > 0) { ec = sampler->start_sampling(); if (ec) { LOGE("HWCPipe: {}", ec.message()); } } } HWCPipeStatsProvider::~HWCPipeStatsProvider() { std::error_code ec; if (stat_data_count > 0) { ec = sampler->stop_sampling(); if (ec) { LOGE("HWCPipe: {}", ec.message()); } } } bool HWCPipeStatsProvider::is_available(StatIndex index) const { return stat_data.find(index) != stat_data.end(); } const StatGraphData &HWCPipeStatsProvider::get_graph_data(StatIndex index) const { assert(is_available(index) && "HWCPipeStatsProvider::get_graph_data() called with invalid StatIndex"); static StatGraphData vertex_compute_cycles{"Vertex Compute Cycles", "{:4.1f} M/s", static_cast(1e-6)}; // HWCPipe reports combined vertex/compute cycles (which is Arm specific) // Ensure we report graph with the correct name when asked for vertex cycles if (index == StatIndex::gpu_vertex_cycles) { return vertex_compute_cycles; } return default_graph_map[index]; } static double get_gpu_counter_value(const hwcpipe::counter_sample &sample) { switch (sample.type) { case hwcpipe::counter_sample::type::uint64: return sample.value.uint64; case hwcpipe::counter_sample::type::float64: return sample.value.float64; default: return 0.0; } } StatsProvider::Counters HWCPipeStatsProvider::sample(float delta_time) { Counters res; if (stat_data_count < 1) { return res; } std::error_code ec; ec = sampler->sample_now(); if (ec) { LOGE("HWCPipe: {}", ec.message()); } else { // Map from hwcpipe measurement to our sample result for each counter for (auto iter : stat_data) { StatIndex index = iter.first; const StatData &data = iter.second; hwcpipe::counter_sample sample; ec = sampler->get_counter_value(data.counter, sample); if (ec) { LOGE("HWCPipe: {}", ec.message()); continue; } auto d = get_gpu_counter_value(sample); if (data.scaling == StatScaling::ByDeltaTime && delta_time != 0.0f) { d /= delta_time; } else if (data.scaling == StatScaling::ByCounter) { ec = sampler->get_counter_value(data.divisor, sample); if (ec) { LOGE("HWCPipe: {}", ec.message()); continue; } double divisor = get_gpu_counter_value(sample); if (divisor != 0.0) { d /= divisor; } else { d = 0.0; } } res[index].result = d; } } return res; } StatsProvider::Counters HWCPipeStatsProvider::continuous_sample(float delta_time) { return sample(delta_time); } } // namespace vkb