Skip to content
12 changes: 8 additions & 4 deletions src/bvar/default_variables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <unistd.h> // getpagesize
#include <sys/types.h>
#include <sys/resource.h> // getrusage
#include <sys/utsname.h> // uname
#include <dirent.h> // dirent
#include <iomanip> // setw
#include <stdio.h>
Expand All @@ -39,6 +40,7 @@
#include "butil/process_util.h" // ReadCommandLine
#include "butil/popen.h" // read_command_output
#include "bvar/passive_status.h"
#include "bvar/default_variables.h" // make_kernel_version_string

namespace bvar {

Expand Down Expand Up @@ -617,12 +619,14 @@ static void get_cmdline(std::ostream& os, void*) {
struct ReadVersion {
std::string content;
ReadVersion() {
std::ostringstream oss;
if (butil::read_command_output(oss, "uname -ap") != 0) {
LOG(ERROR) << "Fail to read kernel version";
struct utsname buf;
if (uname(&buf) != 0) {
const int saved_errno = errno;
LOG(ERROR) << "Failed to read kernel version, errno=" << saved_errno
<< " (" << berror(saved_errno) << ")";
return;
}
Comment on lines +622 to 628
Comment on lines +622 to 628
Comment on lines +622 to 628
Comment on lines +622 to 628
content.append(oss.str());
content.append(make_kernel_version_string(buf));
}
};
static void get_kernel_version(std::ostream& os, void*) {
Expand Down
64 changes: 64 additions & 0 deletions src/bvar/default_variables.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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.

#ifndef BVAR_DEFAULT_VARIABLES_H
#define BVAR_DEFAULT_VARIABLES_H

#include <sys/utsname.h> // struct utsname
#include <sstream> // std::ostringstream
#include <string> // std::string

namespace bvar {

// Build the value of the `kernel_version` bvar from a uname(2) result.
// The field layout matches `uname -ap` on Linux and macOS:
// Linux : sysname nodename release version machine processor machine GNU/Linux
// macOS : sysname nodename release version machine processor
Comment on lines +27 to +30
//
// uname(2) exposes no separate processor (-p) or hardware-platform (-i)
// field, so both fall back to `machine`. That matches `uname -ap` on the
// platforms brpc targets (Linux/macOS); treat it as best-effort elsewhere.
//
// This is intentionally a header-only helper so that it is shared by both
// default_variables.cpp and the unit tests. default_variables.o is stripped
// from unit-test binaries (see BVAR_NOT_LINK_DEFAULT_VARIABLES in
// variable.cpp), so keeping the formatting logic here lets tests exercise the
// exact production formatter without depending on that object being linked.
inline std::string make_kernel_version_string(const struct utsname& buf) {
Comment on lines +25 to +41
#if defined(__APPLE__) && (defined(__aarch64__) || defined(__arm64__))
const char* processor = "arm";
#elif defined(__APPLE__) && defined(__x86_64__)
const char* processor = "i386";
#else
const char* processor = buf.machine;
#endif
std::ostringstream oss;
oss << buf.sysname << ' ' << buf.nodename << ' '
<< buf.release << ' ' << buf.version << ' '
<< buf.machine << ' ' << processor;
#if defined(__linux__)
// `uname -a` appends the hardware platform and the operating-system
// identifier on Linux; the hardware platform equals `machine` here.
oss << ' ' << buf.machine << " GNU/Linux";
#endif
Comment on lines +49 to +57
oss << '\n';
return oss.str();
}

} // namespace bvar

#endif // BVAR_DEFAULT_VARIABLES_H
46 changes: 45 additions & 1 deletion test/bvar_variable_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

#include <pthread.h> // pthread_*
#include <unistd.h> // usleep

#include <sys/utsname.h> // uname
#include <cstring> // strlen
#include <cstddef>
#include <memory>
#include <thread>
Expand All @@ -29,6 +30,7 @@
#include "butil/macros.h"

#include "bvar/bvar.h"
#include "bvar/default_variables.h" // make_kernel_version_string

#include <gflags/gflags.h>
#include <gtest/gtest.h>
Expand Down Expand Up @@ -462,6 +464,48 @@ TEST_F(VariableTest, dtor_waits_for_inflight_describe) {

ASSERT_TRUE(destructed.load());
}

TEST_F(VariableTest, kernel_version_contains_uname_fields) {
struct utsname buf;
ASSERT_EQ(0, uname(&buf));

// Each field should be non-empty
ASSERT_GT(std::strlen(buf.sysname), 0u);
ASSERT_GT(std::strlen(buf.nodename), 0u);
ASSERT_GT(std::strlen(buf.release), 0u);
ASSERT_GT(std::strlen(buf.version), 0u);
ASSERT_GT(std::strlen(buf.machine), 0u);

// Exercise the exact formatter that backs the kernel_version bvar. It is a
// header-only helper shared with default_variables.cpp, so this validates
// the real production formatting without depending on default_variables.o
// being linked into the unit-test binary: that object is stripped via
// BVAR_NOT_LINK_DEFAULT_VARIABLES, so the bvar is not registered here and
// describe_exposed("kernel_version") would return nothing.
const std::string content = bvar::make_kernel_version_string(buf);
ASSERT_FALSE(content.empty());

// The formatted value should contain all the key uname fields.
ASSERT_NE(content.find(buf.sysname), std::string::npos);
ASSERT_NE(content.find(buf.nodename), std::string::npos);
ASSERT_NE(content.find(buf.release), std::string::npos);
ASSERT_NE(content.find(buf.version), std::string::npos);
ASSERT_NE(content.find(buf.machine), std::string::npos);

// The trailing newline must be preserved to match the previous
// popen("uname -ap") output that this bvar used to expose.
ASSERT_EQ('\n', content[content.size() - 1]);

// On Linux, sysname is "Linux" and the OS suffix is appended; on macOS,
// sysname is "Darwin" and there is no OS suffix (both match `uname -ap`).
#if defined(__linux__)
ASSERT_STREQ(buf.sysname, "Linux");
ASSERT_NE(content.find("GNU/Linux"), std::string::npos);
#elif defined(__APPLE__)
ASSERT_STREQ(buf.sysname, "Darwin");
ASSERT_EQ(content.find("GNU/Linux"), std::string::npos);
#endif
Comment on lines +501 to +507
Comment on lines +501 to +507
}
} // namespace

int main(int argc, char** argv) {
Expand Down
Loading