blob: be5ae8486619c91c9a1301885ef970f6fdc67ef4 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <sstream>
#include <base/bind.h>
#include <base/callback.h>
#include <base/logging.h>
#include <base/memory/weak_ptr.h>
#include <base/run_loop.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_tokenizer.h>
#include <base/strings/string_util.h>
#include <base/threading/thread_task_runner_handle.h>
#include <base/time/time.h>
#include <re2/re2.h>
#include "diagnostics/telem/telem_connection.h"
#include "diagnostics/telem/telemetry_group_enum.h"
#include "wilco_dtc_supportd.pb.h" // NOLINT(build/include)
namespace diagnostics {
namespace {
// Generic callback which moves the response to its destination. All
// specific parsing logic is left to other methods, allowing this
// single callback to be used for each gRPC request.
template <typename ResponseType>
void OnRpcResponseReceived(std::unique_ptr<ResponseType>* response_destination,
base::Closure run_loop_quit_closure,
std::unique_ptr<ResponseType> response) {
*response_destination = std::move(response);
run_loop_quit_closure.Run();
}
// Mapping between groups and items. Each item belongs to exactly one
// group.
const std::map<TelemetryGroupEnum, std::vector<TelemetryItemEnum>> group_map = {
{TelemetryGroupEnum::kDisk,
{TelemetryItemEnum::kMemTotalMebibytes,
TelemetryItemEnum::kMemFreeMebibytes}}};
} // namespace
TelemConnection::TelemConnection() {
owned_client_impl_ = std::make_unique<AsyncGrpcClientAdapterImpl>();
client_impl_ = owned_client_impl_.get();
}
TelemConnection::TelemConnection(AsyncGrpcClientAdapter* client)
: client_impl_(client) {}
TelemConnection::~TelemConnection() {
ShutdownClient();
}
void TelemConnection::Connect(const std::string& target_uri) {
DCHECK(!client_impl_->IsConnected());
client_impl_->Connect(target_uri);
}
const base::Optional<base::Value> TelemConnection::GetItem(
TelemetryItemEnum item, base::TimeDelta acceptable_age) {
// First, check to see if the desired telemetry information is
// present and valid in the cache. If so, just return it.
if (!cache_.IsValid(item, acceptable_age)) {
// When no valid cached data is present, make a gRPC request to
// obtain the appropriate telemetry data. This may result in
// more data being fetched and cached than just the desired item.
switch (item) {
case TelemetryItemEnum::kUptime:
UpdateProcData(grpc_api::GetProcDataRequest::FILE_UPTIME);
break;
case TelemetryItemEnum::kMemTotalMebibytes: // FALLTHROUGH
case TelemetryItemEnum::kMemFreeMebibytes:
UpdateProcData(grpc_api::GetProcDataRequest::FILE_MEMINFO);
break;
case TelemetryItemEnum::kNumRunnableEntities: // FALLTHROUGH
case TelemetryItemEnum::kNumExistingEntities:
UpdateProcData(grpc_api::GetProcDataRequest::FILE_LOADAVG);
break;
case TelemetryItemEnum::kTotalIdleTimeUserHz: // FALLTHROUGH
case TelemetryItemEnum::kIdleTimePerCPUUserHz:
UpdateProcData(grpc_api::GetProcDataRequest::FILE_STAT);
break;
case TelemetryItemEnum::kAcpiButton:
UpdateProcData(grpc_api::GetProcDataRequest::DIRECTORY_ACPI_BUTTON);
break;
case TelemetryItemEnum::kNetStat:
UpdateProcData(grpc_api::GetProcDataRequest::FILE_NET_NETSTAT);
break;
case TelemetryItemEnum::kNetDev:
UpdateProcData(grpc_api::GetProcDataRequest::FILE_NET_DEV);
break;
case TelemetryItemEnum::kHwmon:
UpdateSysfsData(grpc_api::GetSysfsDataRequest::CLASS_HWMON);
break;
case TelemetryItemEnum::kThermal:
UpdateSysfsData(grpc_api::GetSysfsDataRequest::CLASS_THERMAL);
break;
case TelemetryItemEnum::kDmiTables:
UpdateSysfsData(grpc_api::GetSysfsDataRequest::FIRMWARE_DMI_TABLES);
break;
}
}
return cache_.GetParsedData(item);
}
const std::vector<
std::pair<TelemetryItemEnum, const base::Optional<base::Value>>>
TelemConnection::GetGroup(TelemetryGroupEnum group,
base::TimeDelta acceptable_age) {
std::vector<std::pair<TelemetryItemEnum, const base::Optional<base::Value>>>
telem_items;
for (TelemetryItemEnum item : group_map.at(group))
telem_items.emplace_back(item, GetItem(item, acceptable_age));
return telem_items;
}
// Sends a GetProcDataRequest, then parses and caches the response.
// If the response format for a specific telemetry item is incorrectly
// formatted, that item will be ignored and not cached, but other,
// correctly formatted telemetry items from the same response will
// still be parsed and cached.
void TelemConnection::UpdateProcData(grpc_api::GetProcDataRequest::Type type) {
grpc_api::GetProcDataRequest request;
request.set_type(type);
std::unique_ptr<grpc_api::GetProcDataResponse> response;
base::RunLoop run_loop;
client_impl_->GetProcData(
request, base::Bind(&OnRpcResponseReceived<grpc_api::GetProcDataResponse>,
base::Unretained(&response), run_loop.QuitClosure()));
VLOG(0) << "Sent GetProcDataRequest";
run_loop.Run();
if (!response) {
LOG(ERROR) << "No ProcDataResponse received.";
return;
}
// Parse the response and cache the parsed data.
switch (type) {
case grpc_api::GetProcDataRequest::FILE_UPTIME:
ExtractDataFromProcUptime(*response);
break;
case grpc_api::GetProcDataRequest::FILE_MEMINFO:
ExtractDataFromProcMeminfo(*response);
break;
case grpc_api::GetProcDataRequest::FILE_LOADAVG:
ExtractDataFromProcLoadavg(*response);
break;
case grpc_api::GetProcDataRequest::FILE_STAT:
ExtractDataFromProcStat(*response);
break;
case grpc_api::GetProcDataRequest::DIRECTORY_ACPI_BUTTON:
ExtractDataFromProcAcpiButton(*response);
break;
case grpc_api::GetProcDataRequest::FILE_NET_NETSTAT:
ExtractDataFromProcNetStat(*response);
break;
case grpc_api::GetProcDataRequest::FILE_NET_DEV:
ExtractDataFromProcNetDev(*response);
break;
default:
LOG(ERROR) << "Bad ProcDataRequest type: " << type;
return;
}
}
// Sends a GetSysfsDataRequest, then parses and caches the response.
// If the response format for a specific telemetry item is incorrectly
// formatted, that item will be ignored and not cached, but other,
// correctly formatted telemetry items from the same response will
// still be parsed and cached.
void TelemConnection::UpdateSysfsData(
grpc_api::GetSysfsDataRequest::Type type) {
grpc_api::GetSysfsDataRequest request;
request.set_type(type);
std::unique_ptr<grpc_api::GetSysfsDataResponse> response;
base::RunLoop run_loop;
client_impl_->GetSysfsData(
request,
base::Bind(&OnRpcResponseReceived<grpc_api::GetSysfsDataResponse>,
base::Unretained(&response), run_loop.QuitClosure()));
VLOG(0) << "Sent GetSysfsDataRequest";
run_loop.Run();
if (!response) {
LOG(ERROR) << "No SysfsDataResponse received.";
return;
}
// Parse the response and cache the parsed data.
switch (type) {
case grpc_api::GetSysfsDataRequest::CLASS_HWMON:
ExtractDataFromSysfsHwmon(*response);
break;
case grpc_api::GetSysfsDataRequest::CLASS_THERMAL:
ExtractDataFromSysfsThermal(*response);
break;
case grpc_api::GetSysfsDataRequest::FIRMWARE_DMI_TABLES:
ExtractDataFromSysfsDmiTables(*response);
break;
default:
LOG(ERROR) << "Bad SysfsDataRequest type: " << type;
return;
}
}
void TelemConnection::ShutdownClient() {
base::RunLoop loop;
client_impl_->Shutdown(loop.QuitClosure());
loop.Run();
}
// Parse the raw /proc/meminfo file.
void TelemConnection::ExtractDataFromProcMeminfo(
const grpc_api::GetProcDataResponse& response) {
// Make sure we received exactly one file in the response.
if (response.file_dump_size() != 1) {
LOG(ERROR) << "Bad GetProcDataResponse from request of type FILE_MEMINFO.";
return;
}
// Parse the meminfo response for kMemTotal and kMemFree.
base::StringPairs keyVals;
base::SplitStringIntoKeyValuePairs(response.file_dump(0).contents(), ':',
'\n', &keyVals);
for (int i = 0; i < keyVals.size(); i++) {
if (keyVals[i].first == "MemTotal") {
// Convert from kB to MB and cache the result.
int memtotal_mb;
base::StringTokenizer t(keyVals[i].second, " ");
if (t.GetNext() && base::StringToInt(t.token(), &memtotal_mb) &&
t.GetNext() && t.token() == "kB") {
memtotal_mb /= 1024;
cache_.SetParsedData(
TelemetryItemEnum::kMemTotalMebibytes,
base::Optional<base::Value>(base::Value(memtotal_mb)));
} else {
LOG(ERROR) << "Incorrectly formatted MemTotal.";
}
} else if (keyVals[i].first == "MemFree") {
// Convert from kB to MB and cache the result.
int memfree_mb;
base::StringTokenizer t(keyVals[i].second, " ");
if (t.GetNext() && base::StringToInt(t.token(), &memfree_mb) &&
t.GetNext() && t.token() == "kB") {
memfree_mb /= 1024;
cache_.SetParsedData(
TelemetryItemEnum::kMemFreeMebibytes,
base::Optional<base::Value>(base::Value(memfree_mb)));
} else {
LOG(ERROR) << "Incorrectly formatted MemFree.";
}
}
}
}
void TelemConnection::ExtractDataFromProcUptime(
const grpc_api::GetProcDataResponse& response) {
NOTIMPLEMENTED();
}
void TelemConnection::ExtractDataFromProcLoadavg(
const grpc_api::GetProcDataResponse& response) {
// Make sure we received exactly one file in the response.
if (response.file_dump_size() != 1) {
LOG(ERROR) << "Bad GetProcDataResponse from request of type FILE_LOADAVG.";
return;
}
// We expect the loadavg response to have the following format:
// %f %f %f %d/%d %d. At the moment, we're only interested in
// the %d/%d: it will parse into kNumRunnableEntities/kNumExistingEntities.
// We'll first make sure the entire response matches the expected format,
// then we'll extract the %d/%d.
int running_entities;
int existing_entities;
if (!RE2::FullMatch(response.file_dump(0).contents(),
"\\d+\\.\\d+\\s\\d+\\.\\d+\\s\\d+"
"\\.\\d+\\s(\\d+)/(\\d+)\\s\\d+\\n",
&running_entities, &existing_entities)) {
LOG(ERROR) << "Incorrectly formatted loadavg.";
return;
}
cache_.SetParsedData(
TelemetryItemEnum::kNumRunnableEntities,
base::Optional<base::Value>(base::Value(running_entities)));
cache_.SetParsedData(
TelemetryItemEnum::kNumExistingEntities,
base::Optional<base::Value>(base::Value(existing_entities)));
}
void TelemConnection::ExtractDataFromProcStat(
const grpc_api::GetProcDataResponse& response) {
// Make sure we received exactly one file in the response.
if (response.file_dump_size() != 1) {
LOG(ERROR) << "Bad GetProcDataResponse from request of type FILE_LOADAVG.";
return;
}
// Grab the idle time for all CPUs combined, as well as the idle time
// for each logical CPU. We'll store each of the times as a string in
// case the system has been on for long enough to overflow an int with
// its idle time.
std::string idle_time_combined;
std::stringstream response_sstream(response.file_dump(0).contents());
std::string current_line;
// The first line should be: cpu %d %d %d %d ..., where the last number
// is the idle time.
if (!std::getline(response_sstream, current_line) ||
!RE2::PartialMatch(current_line, "cpu\\s+\\d+ \\d+ \\d+ (\\d+)",
&idle_time_combined)) {
LOG(ERROR) << "Incorrectly formatted stat.";
return;
}
// The next N lines should be: cpu%d %d %d %d %d ..., where N is the number
// of logical CPUs.
std::string idle_time_current_cpu;
base::ListValue logical_cpu_idle_time;
while (std::getline(response_sstream, current_line) &&
RE2::PartialMatch(current_line, "cpu\\d+ \\d+ \\d+ \\d+ (\\d+)",
&idle_time_current_cpu))
logical_cpu_idle_time.AppendString(idle_time_current_cpu);
cache_.SetParsedData(
TelemetryItemEnum::kTotalIdleTimeUserHz,
base::Optional<base::Value>(base::Value(idle_time_combined)));
cache_.SetParsedData(TelemetryItemEnum::kIdleTimePerCPUUserHz,
base::Optional<base::Value>(logical_cpu_idle_time));
}
void TelemConnection::ExtractDataFromProcAcpiButton(
const grpc_api::GetProcDataResponse& response) {
NOTIMPLEMENTED();
}
void TelemConnection::ExtractDataFromProcNetStat(
const grpc_api::GetProcDataResponse& response) {
NOTIMPLEMENTED();
}
void TelemConnection::ExtractDataFromProcNetDev(
const grpc_api::GetProcDataResponse& response) {
NOTIMPLEMENTED();
}
void TelemConnection::ExtractDataFromSysfsHwmon(
const grpc_api::GetSysfsDataResponse& response) {
NOTIMPLEMENTED();
}
void TelemConnection::ExtractDataFromSysfsThermal(
const grpc_api::GetSysfsDataResponse& response) {
NOTIMPLEMENTED();
}
void TelemConnection::ExtractDataFromSysfsDmiTables(
const grpc_api::GetSysfsDataResponse& response) {
NOTIMPLEMENTED();
}
} // namespace diagnostics