Skip to content

Commit

Permalink
adds HostInfo.NativeArchitecture (#200)
Browse files Browse the repository at this point in the history
  • Loading branch information
intxgo authored Feb 12, 2024
1 parent 489579d commit 80edf3d
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 14 deletions.
3 changes: 3 additions & 0 deletions .changelog/200.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
Adds NativeArchitecture to HostInfo to allow applications to detect whether they are running in emulation.
```
38 changes: 37 additions & 1 deletion providers/darwin/arch_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@ package darwin

import (
"fmt"
"os"

"golang.org/x/sys/unix"
)

const hardwareMIB = "hw.machine"
const (
hardwareMIB = "hw.machine"
procTranslated = "sysctl.proc_translated"
archIntel = "x86_64"
archApple = "arm64"
)

func Architecture() (string, error) {
arch, err := unix.Sysctl(hardwareMIB)
Expand All @@ -35,3 +41,33 @@ func Architecture() (string, error) {

return arch, nil
}

func NativeArchitecture() (string, error) {
processArch, err := Architecture()
if err != nil {
return "", err
}

// https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment

translated, err := unix.SysctlUint32(procTranslated)
if err != nil {
// macos without Rosetta installed doesn't have sysctl.proc_translated
if os.IsNotExist(err) {
return processArch, nil
}
return "", fmt.Errorf("failed to read sysctl.proc_translated: %w", err)
}

var nativeArch string

switch translated {
case 0:
nativeArch = processArch
case 1:
// Rosetta 2 is supported only on Apple silicon
nativeArch = archApple
}

return nativeArch, nil
}
6 changes: 6 additions & 0 deletions providers/darwin/arch_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ func TestArchitecture(t *testing.T) {
assert.NoError(t, err)
assert.NotEmpty(t, a)
}

func TestNativeArchitecture(t *testing.T) {
a, err := NativeArchitecture()
assert.NoError(t, err)
assert.NotEmpty(t, a)
}
9 changes: 9 additions & 0 deletions providers/darwin/host_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ func newHost() (*host, error) {
h := &host{}
r := &reader{}
r.architecture(h)
r.nativeArchitecture(h)
r.bootTime(h)
r.hostname(h)
r.network(h)
Expand Down Expand Up @@ -206,6 +207,14 @@ func (r *reader) architecture(h *host) {
h.info.Architecture = v
}

func (r *reader) nativeArchitecture(h *host) {
v, err := NativeArchitecture()
if r.addErr(err) {
return
}
h.info.NativeArchitecture = v
}

func (r *reader) bootTime(h *host) {
v, err := BootTime()
if r.addErr(err) {
Expand Down
45 changes: 45 additions & 0 deletions providers/linux/arch_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@ package linux

import (
"fmt"
"os"
"strings"
"syscall"
)

const (
procSysKernelArch = "/proc/sys/kernel/arch"
procVersion = "/proc/version"
archAmd64 = "amd64"
archArm64 = "arm64"
archAarch64 = "aarch64"
)

func Architecture() (string, error) {
var uname syscall.Utsname
if err := syscall.Uname(&uname); err != nil {
Expand All @@ -38,3 +48,38 @@ func Architecture() (string, error) {

return string(data), nil
}

func NativeArchitecture() (string, error) {
// /proc/sys/kernel/arch was introduced in Kernel 6.1
// https://www.kernel.org/doc/html/v6.1/admin-guide/sysctl/kernel.html#arch
// It's the same as uname -m, except that for a process running in emulation
// machine returned from syscall reflects the emulated machine, whilst /proc
// filesystem is read as file so its value is not emulated
data, err := os.ReadFile(procSysKernelArch)
if err != nil {
if os.IsNotExist(err) {
// fallback to checking version string for older kernels
version, err := os.ReadFile(procVersion)
if err != nil {
return "", nil
}

versionStr := string(version)
if strings.Contains(versionStr, archAmd64) {
return archAmd64, nil
} else if strings.Contains(versionStr, archArm64) {
// for parity with Architecture() and /proc/sys/kernel/arch
// as aarch64 and arm64 are used interchangeably
return archAarch64, nil
}
return "", nil
}

return "", fmt.Errorf("failed to read kernel arch: %w", err)
}

nativeArch := string(data)
nativeArch = strings.TrimRight(nativeArch, "\n")

return string(data), nil
}
36 changes: 36 additions & 0 deletions providers/linux/arch_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. 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.

package linux

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestArchitecture(t *testing.T) {
a, err := Architecture()
assert.NoError(t, err)
assert.NotEmpty(t, a)
}

func TestNativeArchitecture(t *testing.T) {
a, err := NativeArchitecture()
assert.NoError(t, err)
assert.NotEmpty(t, a)
}
9 changes: 9 additions & 0 deletions providers/linux/host_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ func newHost(fs procFS) (*host, error) {
h := &host{stat: stat, procFS: fs}
r := &reader{}
r.architecture(h)
r.nativeArchitecture(h)
r.bootTime(h)
r.containerized(h)
r.hostname(h)
Expand Down Expand Up @@ -197,6 +198,14 @@ func (r *reader) architecture(h *host) {
h.info.Architecture = v
}

func (r *reader) nativeArchitecture(h *host) {
v, err := NativeArchitecture()
if r.addErr(err) {
return
}
h.info.NativeArchitecture = v
}

func (r *reader) bootTime(h *host) {
v, err := bootTime(h.procFS.FS)
if r.addErr(err) {
Expand Down
36 changes: 34 additions & 2 deletions providers/windows/arch_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,46 @@
package windows

import (
windows "github.com/elastic/go-windows"
"golang.org/x/sys/windows"

gowindows "github.com/elastic/go-windows"
)

const (
imageFileMachineAmd64 = 0x8664
imageFileMachineArm64 = 0xAA64
archIntel = "x86_64"
archArm64 = "arm64"
)

func Architecture() (string, error) {
systemInfo, err := windows.GetNativeSystemInfo()
systemInfo, err := gowindows.GetNativeSystemInfo()
if err != nil {
return "", err
}

return systemInfo.ProcessorArchitecture.String(), nil
}

func NativeArchitecture() (string, error) {
var processMachine, nativeMachine uint16
// the pseudo handle doesn't need to be closed
var currentProcessHandle = windows.CurrentProcess()

err := windows.IsWow64Process2(currentProcessHandle, &processMachine, &nativeMachine)
if err != nil {
return "", err
}

var nativeArch string

switch nativeMachine {
case imageFileMachineAmd64:
// for parity with Architecture() as amd64 and x86_64 are used interchangeably
nativeArch = archIntel
case imageFileMachineArm64:
nativeArch = archArm64
}

return nativeArch, nil
}
36 changes: 36 additions & 0 deletions providers/windows/arch_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. 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.

package windows

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestArchitecture(t *testing.T) {
a, err := Architecture()
assert.NoError(t, err)
assert.NotEmpty(t, a)
}

func TestNativeArchitecture(t *testing.T) {
a, err := NativeArchitecture()
assert.NoError(t, err)
assert.NotEmpty(t, a)
}
9 changes: 9 additions & 0 deletions providers/windows/host_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func newHost() (*host, error) {
h := &host{}
r := &reader{}
r.architecture(h)
r.nativeArchitecture(h)
r.bootTime(h)
r.hostname(h)
r.network(h)
Expand Down Expand Up @@ -141,6 +142,14 @@ func (r *reader) architecture(h *host) {
h.info.Architecture = v
}

func (r *reader) nativeArchitecture(h *host) {
v, err := NativeArchitecture()
if r.addErr(err) {
return
}
h.info.NativeArchitecture = v
}

func (r *reader) bootTime(h *host) {
v, err := BootTime()
if r.addErr(err) {
Expand Down
23 changes: 12 additions & 11 deletions types/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,18 @@ type VMStat interface {

// HostInfo contains basic host information.
type HostInfo struct {
Architecture string `json:"architecture"` // Hardware architecture (e.g. x86_64, arm, ppc, mips).
BootTime time.Time `json:"boot_time"` // Host boot time.
Containerized *bool `json:"containerized,omitempty"` // Is the process containerized.
Hostname string `json:"name"` // Hostname, lowercased.
IPs []string `json:"ip,omitempty"` // List of all IPs.
KernelVersion string `json:"kernel_version"` // Kernel version.
MACs []string `json:"mac"` // List of MAC addresses.
OS *OSInfo `json:"os"` // OS information.
Timezone string `json:"timezone"` // System timezone.
TimezoneOffsetSec int `json:"timezone_offset_sec"` // Timezone offset (seconds from UTC).
UniqueID string `json:"id,omitempty"` // Unique ID of the host (optional).
Architecture string `json:"architecture"` // Process hardware architecture (e.g. x86_64, arm, ppc, mips).
NativeArchitecture string `json:"native_architecture"` // Native OS hardware architecture (e.g. x86_64, arm, ppc, mips).
BootTime time.Time `json:"boot_time"` // Host boot time.
Containerized *bool `json:"containerized,omitempty"` // Is the process containerized.
Hostname string `json:"name"` // Hostname, lowercased.
IPs []string `json:"ip,omitempty"` // List of all IPs.
KernelVersion string `json:"kernel_version"` // Kernel version.
MACs []string `json:"mac"` // List of MAC addresses.
OS *OSInfo `json:"os"` // OS information.
Timezone string `json:"timezone"` // System timezone.
TimezoneOffsetSec int `json:"timezone_offset_sec"` // Timezone offset (seconds from UTC).
UniqueID string `json:"id,omitempty"` // Unique ID of the host (optional).
}

// Uptime returns the system uptime
Expand Down

0 comments on commit 80edf3d

Please sign in to comment.