blob: 25d9f028566aa4de6340609bbab12b60716a3391 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 "util/disk-info.h"
#include <regex>
#ifdef __APPLE__
#include <sys/mount.h>
#else
#include <sys/vfs.h>
#endif
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>
#include <unistd.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/join.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
#include "util/debug-util.h"
#include "common/names.h"
using boost::algorithm::is_any_of;
using boost::algorithm::split;
using boost::algorithm::token_compress_on;
using boost::algorithm::trim;
using boost::algorithm::trim_right_if;
namespace impala {
bool DiskInfo::initialized_;
vector<DiskInfo::Disk> DiskInfo::disks_;
map<dev_t, int> DiskInfo::device_id_to_disk_id_;
map<string, int> DiskInfo::disk_name_to_disk_id_;
bool DiskInfo::TryNVMETrim(const std::string& name_in, std::string* basename_out) {
// NVME drives do not follow the typical device naming pattern. The pattern for NVME
// drives is nvme{device_id}n{namespace_id}p{partition_id}. The appropriate thing
// to do for this pattern is to trim the "p{partition_id}" part.
std::regex nvme_regex = std::regex("(nvme[0-9]+n[0-9]+)(p[0-9]+)*");
std::smatch nvme_match_result;
if (std::regex_match(name_in, nvme_match_result, nvme_regex)) {
DCHECK_GE(nvme_match_result.size(), 2);
// Match 0 contains the whole string.
// Match 1 contains the base nvme device without the partition.
*basename_out = nvme_match_result[1];
return true;
}
return false;
}
// Parses /proc/partitions to get the number of disks. A bit of looking around
// seems to indicate this as the best way to do this.
// TODO: is there not something better than this?
void DiskInfo::GetDeviceNames() {
// Format of this file is:
// major, minor, #blocks, name
// We are only interesting in name which is formatted as device_name<partition #>
// The same device will show up multiple times for each partition (e.g. sda1, sda2).
ifstream partitions("/proc/partitions", ios::in);
while (partitions.good() && !partitions.eof()) {
string line;
getline(partitions, line);
trim(line);
vector<string> fields;
split(fields, line, is_any_of(" "), token_compress_on);
if (fields.size() != 4) continue;
string name = fields[3];
if (name == "name") continue;
// NVME devices have a special format. Try to detect that, falling back to the normal
// method if this is not an NVME device.
std::string nvme_basename;
if (TryNVMETrim(name, &nvme_basename)) {
// This is an NVME device, use the returned basename
name = nvme_basename;
} else {
// Does not follow the NVME pattern, so use the logic for a normal disk device
// Remove the partition# from the name. e.g. sda2 --> sda
trim_right_if(name, is_any_of("0123456789"));
}
// Create a mapping of all device ids (one per partition) to the disk id.
int major_dev_id = atoi(fields[0].c_str());
int minor_dev_id = atoi(fields[1].c_str());
dev_t dev = makedev(major_dev_id, minor_dev_id);
DCHECK(device_id_to_disk_id_.find(dev) == device_id_to_disk_id_.end());
int disk_id = -1;
map<string, int>::iterator it = disk_name_to_disk_id_.find(name);
if (it == disk_name_to_disk_id_.end()) {
// First time seeing this disk
disk_id = disks_.size();
disks_.push_back(Disk(name, disk_id));
disk_name_to_disk_id_[name] = disk_id;
} else {
disk_id = it->second;
}
device_id_to_disk_id_[dev] = disk_id;
}
if (partitions.is_open()) partitions.close();
if (disks_.empty()) {
// If all else fails, return 1
LOG(WARNING) << "Could not determine number of disks on this machine.";
disks_.push_back(Disk("sda", 0));
return;
}
// Determine if the disk is rotational or not.
for (int i = 0; i < disks_.size(); ++i) {
// We can check if it is rotational by reading:
// /sys/block/<device>/queue/rotational
// If the file is missing or has unexpected data, default to rotational.
stringstream ss;
ss << "/sys/block/" << disks_[i].name << "/queue/rotational";
ifstream rotational(ss.str().c_str(), ios::in);
if (rotational.good()) {
string line;
getline(rotational, line);
if (line == "0") disks_[i].is_rotational = false;
}
if (rotational.is_open()) rotational.close();
}
}
void DiskInfo::Init() {
GetDeviceNames();
initialized_ = true;
}
int DiskInfo::disk_id(const char* path) {
struct stat s;
stat(path, &s);
map<dev_t, int>::iterator it = device_id_to_disk_id_.find(s.st_dev);
if (it == device_id_to_disk_id_.end()) return -1;
return it->second;
}
string DiskInfo::DebugString() {
DCHECK(initialized_);
stringstream stream;
stream << "Disk Info: " << endl;
stream << " Num disks " << num_disks() << ": " << endl;
for (int i = 0; i < disks_.size(); ++i) {
stream << " " << disks_[i].name
<< " (rotational=" << (disks_[i].is_rotational ? "true" : "false") << ")\n";
}
stream << endl;
return stream.str();
}
}