blob: 51c5ce3f18c3a84760ec3476e07d57e439accc5b [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/mem-info.h"
#include "util/debug-util.h"
#include "util/string-parser.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <cctype>
#include <fstream>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include "util/cgroup-util.h"
#include "util/parse-util.h"
#include "util/pretty-printer.h"
#include "common/names.h"
using boost::algorithm::is_any_of;
using boost::algorithm::split;
using boost::algorithm::token_compress_on;
DECLARE_string(mem_limit);
namespace impala {
bool MemInfo::initialized_ = false;
int64_t MemInfo::physical_mem_ = -1;
int32_t MemInfo::vm_overcommit_ = -1;
int64_t MemInfo::commit_limit_ = -1;
// Lines in meminfo have the form key colon whitespace value for example:
// MemTotal: 16129508 kB
int64_t ParseMemString(const char* val, size_t len) {
StringParser::ParseResult result;
int64_t mem_total_kb = StringParser::StringToInt<int64_t>(val,
len, &result);
if (result != StringParser::PARSE_SUCCESS) return -1;
// Entries in /proc/meminfo are in KB.
return mem_total_kb * 1024L;
}
void MemInfo::Init() {
// Read overcommit settings
ParseOvercommit();
// Read from /proc/meminfo
ifstream meminfo("/proc/meminfo", ios::in);
string line;
while (meminfo.good() && !meminfo.eof()) {
getline(meminfo, line);
vector<string> fields;
split(fields, line, is_any_of(" "), token_compress_on);
// We expect lines such as, e.g., 'MemTotal: 16129508 kB'
if (fields.size() < 3) continue;
// Make sure that the format of the file does not change
DCHECK_EQ(fields[2], "kB");
if (fields[0].compare("MemTotal:") == 0) {
physical_mem_ = ParseMemString(fields[1].data(), fields[1].size());
} else if (fields[0].compare("CommitLimit:") == 0) {
commit_limit_ = ParseMemString(fields[1].data(), fields[1].size());
}
}
if (meminfo.is_open()) meminfo.close();
if (physical_mem_ == -1) {
LOG(WARNING) << "Could not determine amount of physical memory on this machine "
<< "using /proc/meminfo.";
}
if (commit_limit_ == -1) {
LOG(WARNING) << "Could not determine memory commit limit on this machine "
<< "using /proc/meminfo.";
}
initialized_ = true;
}
void MemInfo::ParseOvercommit() {
ifstream overcommit_s("/proc/sys/vm/overcommit_memory", ios::in);
overcommit_s >> vm_overcommit_;
}
bool MemInfo::HaveSmaps() {
MappedMemInfo result;
ifstream smaps("/proc/self/smaps", ios::in);
return smaps.good();
}
MappedMemInfo MemInfo::ParseSmaps() {
MappedMemInfo result;
ifstream smaps("/proc/self/smaps", ios::in);
if (!smaps) {
LOG_FIRST_N(INFO, 1) << "Could not open smaps";
return result;
}
while (smaps) {
string line;
getline(smaps, line);
if (line.empty()) continue;
if (isdigit(line[0]) || (line[0] >= 'a' && line[0] <= 'f')) {
// Line is the start of a new mapping, of form:
// 561ceff9c000-561ceffa1000 rw-p 00000000 00:00 0
// We distinguish this case by checking for lower-case hex digits.
++result.num_maps;
continue;
}
// Line is in the form of <Name>:<spaces><value>, e.g.:
// Size: 1084 kB
// VmFlags: rd ex mr mw me dw
size_t colon_pos = line.find(':');
if (colon_pos == string::npos) continue;
string name = line.substr(0, colon_pos);
size_t non_space_after_colon_pos = line.find_first_not_of(' ', colon_pos + 1);
if (non_space_after_colon_pos == string::npos) continue;
// From the first non-space after the colon through the end of the string.
string value = line.substr(non_space_after_colon_pos);
// Use atol() to parse the value, ignoring " kB" suffix.
if (name == "Size") {
result.size_kb += atol(value.c_str());
} else if (name == "Rss") {
result.rss_kb += atol(value.c_str());
} else if (name == "AnonHugePages") {
result.anon_huge_pages_kb += atol(value.c_str());
}
}
return result;
}
ThpConfig MemInfo::ParseThpConfig() {
ThpConfig result;
result.enabled = GetThpConfigVal("enabled");
result.defrag = GetThpConfigVal("defrag");
result.khugepaged_defrag = GetThpConfigVal("khugepaged/defrag");
return result;
}
string MemInfo::GetThpConfigVal(const string& relative_path) {
// This is the standard location for the configs.
ifstream file("/sys/kernel/mm/transparent_hugepage/" + relative_path);
if (!file) {
// Some earlier versions of CentOS/RHEL put the configs in a different place.
file.open("/sys/kernel/mm/redhat_transparent_hugepage/" + relative_path);
if (!file) {
LOG_FIRST_N(INFO, 1) << "Could not open thp config: " << relative_path;
return "<unknown>";
}
}
string result;
getline(file, result);
return result;
}
string MemInfo::DebugString() {
DCHECK(initialized_);
stringstream stream;
stream << "Physical Memory: " << PrettyPrinter::Print(physical_mem_, TUnit::BYTES)
<< endl;
stream << ParseThpConfig().DebugString();
return stream.str();
}
string MappedMemInfo::DebugString() const {
stringstream stream;
stream << "Number of mappings: " << num_maps << endl;
stream << "Total mapping (kB): " << size_kb << endl;
stream << "RSS (kB): " << rss_kb << endl;
stream << "Anon huge pages (kB): " << anon_huge_pages_kb << endl;
return stream.str();
}
string ThpConfig::DebugString() const {
stringstream stream;
stream << "Transparent Huge Pages Config:" << endl;
stream << " enabled: " << enabled << endl;
stream << " defrag: " << defrag << endl;
stream << " khugepaged defrag: " << khugepaged_defrag << endl;
return stream.str();
}
Status ChooseProcessMemLimit(int64_t* bytes_limit, int64_t* process_avail_mem) {
// Depending on the system configuration, we detect the total amount of memory
// available to the system - either the available physical memory, or if overcommitting
// is turned off, we use the memory commit limit from /proc/meminfo (see IMPALA-1690).
// The 'memory' CGroup can also impose a lower limit on memory consumption,
// so we take the minimum of the system memory and the CGroup memory limit.
int64_t avail_mem = MemInfo::physical_mem();
bool use_commit_limit =
MemInfo::vm_overcommit() == 2 && MemInfo::commit_limit() < MemInfo::physical_mem();
if (use_commit_limit) {
avail_mem = MemInfo::commit_limit();
// There might be the case of misconfiguration, when on a system swap is disabled
// and overcommitting is turned off the actual usable memory is less than the
// available physical memory.
LOG(WARNING) << "This system shows a discrepancy between the available "
<< "memory and the memory commit limit allowed by the "
<< "operating system. ( Mem: " << MemInfo::physical_mem()
<< "<=> CommitLimit: " << MemInfo::commit_limit() << "). "
<< "Impala will adhere to the smaller value when setting the process "
<< "memory limit. Please verify the system configuration. Specifically, "
<< "/proc/sys/vm/overcommit_memory and "
<< "/proc/sys/vm/overcommit_ratio.";
}
LOG(INFO) << "System memory available: " << PrettyPrinter::PrintBytes(avail_mem)
<< " (from " << (use_commit_limit ? "commit limit" : "physical mem") << ")";
int64_t cgroup_mem_limit;
Status cgroup_mem_status = CGroupUtil::FindCGroupMemLimit(&cgroup_mem_limit);
if (cgroup_mem_status.ok()) {
if (cgroup_mem_limit < avail_mem) {
avail_mem = cgroup_mem_limit;
LOG(INFO) << "CGroup memory limit for this process reduces physical memory "
<< "available to: " << PrettyPrinter::PrintBytes(avail_mem);
}
} else {
LOG(WARNING) << "Could not detect CGroup memory limit, assuming unlimited: $0"
<< cgroup_mem_status.GetDetail();
}
bool is_percent;
*bytes_limit = ParseUtil::ParseMemSpec(FLAGS_mem_limit, &is_percent, avail_mem);
// ParseMemSpec() returns -1 for invalid input and 0 to mean unlimited. From Impala
// 2.11 onwards we do not support unlimited process memory limits.
if (*bytes_limit <= 0) {
return Status(Substitute("The process memory limit (--mem_limit) must be a positive "
"bytes value or percentage: $0",
FLAGS_mem_limit));
}
if (is_percent) {
LOG(INFO) << "Using process memory limit: " << PrettyPrinter::PrintBytes(*bytes_limit)
<< " (--mem_limit=" << FLAGS_mem_limit << " of "
<< PrettyPrinter::PrintBytes(avail_mem) << ")";
} else {
LOG(INFO) << "Using process memory limit: " << PrettyPrinter::PrintBytes(*bytes_limit)
<< " (--mem_limit=" << FLAGS_mem_limit << ")";
}
if (*bytes_limit > MemInfo::physical_mem()) {
LOG(WARNING) << "Process memory limit " << PrettyPrinter::PrintBytes(*bytes_limit)
<< " exceeds physical memory of "
<< PrettyPrinter::PrintBytes(MemInfo::physical_mem());
}
if (cgroup_mem_status.ok() && *bytes_limit > cgroup_mem_limit) {
LOG(WARNING) << "Process Memory limit " << PrettyPrinter::PrintBytes(*bytes_limit)
<< " exceeds CGroup memory limit of "
<< PrettyPrinter::PrintBytes(cgroup_mem_limit);
}
if (process_avail_mem) *process_avail_mem = avail_mem;
return Status::OK();
}
}