otelcollector/shared/process_utilities_windows.go (398 lines of code) (raw):
package shared
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"time"
"unsafe"
)
// IsProcessRunning checks if a process with the given name is running on the system
func IsProcessRunning(processName string) bool {
osType := os.Getenv("OS_TYPE")
switch osType {
case "linux":
return isProcessRunningLinux(processName)
case "windows":
return isProcessRunningWindows(processName)
default:
fmt.Println("Unsupported OS_TYPE:", osType)
return false
}
}
// Linux implementation using the /proc directory
func isProcessRunningLinux(processName string) bool {
pid := os.Getpid()
dir, err := os.Open("/proc")
if err != nil {
fmt.Println("Error opening /proc:", err)
return false
}
defer dir.Close()
procs, err := dir.Readdirnames(0)
if err != nil {
fmt.Println("Error reading /proc:", err)
return false
}
for _, proc := range procs {
if _, err := os.Stat("/proc/" + proc + "/cmdline"); err == nil {
cmdline, err := os.ReadFile("/proc/" + proc + "/cmdline")
if err == nil && strings.Contains(string(cmdline), processName) {
if proc != fmt.Sprintf("%d", pid) {
return true
}
}
}
}
return false
}
type ProcessEntry32 struct {
Size uint32
CntUsage uint32
ProcessID uint32
DefaultHeapID uintptr
ModuleID uint32
CntThreads uint32
ParentProcessID uint32
PriorityClassBase int32
Flags uint32
ExeFile [260]uint16 // Process name
}
// Windows implementation using syscalls
func isProcessRunningWindows(processName string) bool {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
procSnapshot := kernel32.NewProc("CreateToolhelp32Snapshot")
procProcessFirst := kernel32.NewProc("Process32FirstW")
procProcessNext := kernel32.NewProc("Process32NextW")
handle, _, _ := procSnapshot.Call(2, 0) // TH32CS_SNAPPROCESS
if handle == 0 {
fmt.Println("Error getting snapshot of processes")
return false
}
defer syscall.CloseHandle(syscall.Handle(handle))
var entry ProcessEntry32
entry.Size = uint32(unsafe.Sizeof(entry))
// Get the first process
ret, _, _ := procProcessFirst.Call(handle, uintptr(unsafe.Pointer(&entry)))
for ret != 0 {
// Convert UTF-16 file name to string
exeFile := syscall.UTF16ToString(entry.ExeFile[:])
// Case-insensitive comparison
if strings.EqualFold(exeFile, processName) {
return true
}
// Move to the next process
ret, _, _ = procProcessNext.Call(handle, uintptr(unsafe.Pointer(&entry)))
}
return false
}
// SetEnvAndSourceBashrcOrPowershell sets a key-value pair as an environment variable.
// If OS_TYPE is 'linux', it sets the variable in the .bashrc file and sources it.
// If OS_TYPE is 'windows', it sets the variable in the system environment.
func SetEnvAndSourceBashrcOrPowershell(key, value string, echo bool) error {
// Get the OS_TYPE from environment variables
osType := os.Getenv("OS_TYPE")
if osType == "linux" {
fmt.Println(("Should never reach here as this is the windows file"))
} else if osType == "windows" {
// On Windows, set the environment variable for the machine (persistent across sessions)
cmd := exec.Command("setx", key, value, "/M") // "/M" flag sets the variable for the machine
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to set environment variable on Windows (Machine scope): %v", err)
}
// Set the environment variable for the current process
err := os.Setenv(key, value)
if err != nil {
return fmt.Errorf("failed to set environment variable for current session: %v", err)
}
} else {
return fmt.Errorf("unsupported OS_TYPE: %s", osType)
}
// Conditionally call EchoVar
if echo {
EchoVar(key, value)
}
return nil
}
func StartCommandWithOutputFile(command string, args []string, outputFile string) (int, error) {
cmd := exec.Command(command, args...)
// Set environment variables from os.Environ()
cmd.Env = append(os.Environ())
// Create file to write stdout and stderr
file, err := os.Create(outputFile)
if err != nil {
return 0, fmt.Errorf("error creating output file: %v", err)
}
// Create pipes to capture stdout and stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
return 0, fmt.Errorf("error creating stdout pipe: %v", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
return 0, fmt.Errorf("error creating stderr pipe: %v", err)
}
// Start the command
if err := cmd.Start(); err != nil {
return 0, fmt.Errorf("error starting command: %v", err)
}
// Create a wait group to wait for goroutines
var wg sync.WaitGroup
wg.Add(2)
// Create goroutines to continuously read and write stdout and stderr
go func() {
defer wg.Done()
if _, err := io.Copy(file, stdout); err != nil {
fmt.Printf("Error copying stdout to file: %v\n", err)
}
}()
go func() {
defer wg.Done()
if _, err := io.Copy(file, stderr); err != nil {
fmt.Printf("Error copying stderr to file: %v\n", err)
}
}()
// Wait for both goroutines to finish before closing the file
go func() {
wg.Wait()
file.Close()
}()
// Get the PID of the started process
process_pid := cmd.Process.Pid
return process_pid, nil
}
func StartCommand(command string, args ...string) {
cmd := exec.Command(command, args...)
// Set environment variables from os.Environ()
cmd.Env = append(os.Environ())
// Create pipes to capture stdout and stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Printf("Error creating stdout pipe: %v\n", err)
return
}
stderr, err := cmd.StderrPipe()
if err != nil {
fmt.Printf("Error creating stderr pipe: %v\n", err)
return
}
// Start the command
err = cmd.Start()
if err != nil {
fmt.Printf("Error starting command: %v\n", err)
return
}
// Create goroutines to capture and print stdout and stderr
go func() {
stdoutBytes, _ := io.ReadAll(io.Reader(stdout))
fmt.Print(string(stdoutBytes))
}()
go func() {
stderrBytes, _ := io.ReadAll(io.Reader(stderr))
fmt.Print(string(stderrBytes))
}()
}
func StartCommandAndWait(command string, args ...string) error {
cmd := exec.Command(command, args...)
// Set environment variables from os.Environ()
cmd.Env = append(os.Environ())
// Create pipes to capture stdout and stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("error creating stdout pipe: %v", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("error creating stderr pipe: %v", err)
}
// Start the command
err = cmd.Start()
if err != nil {
return fmt.Errorf("error starting command: %v", err)
}
// Create goroutines to capture and print stdout and stderr
go func() {
stdoutBytes, _ := io.ReadAll(io.Reader(stdout))
fmt.Print(string(stdoutBytes))
}()
go func() {
stderrBytes, _ := io.ReadAll(io.Reader(stderr))
fmt.Print(string(stderrBytes))
}()
// Wait for the command to finish
err = cmd.Wait()
if err != nil {
return fmt.Errorf("error waiting for command: %v", err)
}
return nil
}
func copyOutputMulti(src io.Reader, dest io.Writer, file *os.File) {
// Create a multi-writer to write to both the file and os.Stdout/os.Stderr
multiWriter := io.MultiWriter(dest, file)
_, err := io.Copy(multiWriter, src)
if err != nil {
fmt.Printf("Error copying output: %v\n", err)
}
}
func copyOutputPipe(src io.Reader, dest io.Writer) {
_, err := io.Copy(dest, src)
if err != nil {
fmt.Printf("Error copying output: %v\n", err)
}
}
func copyOutputFile(src io.Reader, file *os.File) {
_, err := io.Copy(file, src)
if err != nil {
fmt.Printf("Error copying output: %v\n", err)
}
}
// StartMetricsExtensionForOverlay starts the MetricsExtension process based on the OS
func StartMetricsExtensionForOverlay(meConfigFile string) (int, error) {
osType := os.Getenv("OS_TYPE")
var cmd *exec.Cmd
switch osType {
case "linux":
cmd = exec.Command("/usr/sbin/MetricsExtension", "-Logger", "File", "-LogLevel", "Info", "-LocalControlChannel", "-TokenSource", "AMCS", "-DataDirectory", "/etc/mdsd.d/config-cache/metricsextension", "-Input", "otlp_grpc_prom", "-ConfigOverridesFilePath", meConfigFile)
case "windows":
// Prepare the command and its arguments
cmd = exec.Command(
"C:\\opt\\metricextension\\MetricsExtension\\MetricsExtension.Native.exe",
"-Logger", "File",
"-LogLevel", "Info",
"-LocalControlChannel",
"-TokenSource", "AMCS",
"-DataDirectory", "C:\\opt\\genevamonitoringagent\\datadirectory\\mcs\\metricsextension\\",
"-Input", "otlp_grpc_prom",
"-ConfigOverridesFilePath", meConfigFile,
)
}
cmd.Env = append(os.Environ())
err := cmd.Start()
if err != nil {
return 0, fmt.Errorf("error starting MetricsExtension: %v", err)
}
return cmd.Process.Pid, nil
}
func StartMetricsExtensionWithConfigOverridesForUnderlay(configOverrides string) {
cmd := exec.Command("/usr/sbin/MetricsExtension", "-Logger", "Console", "-LogLevel", "Error", "-LocalControlChannel", "-TokenSource", "AMCS", "-DataDirectory", "/etc/mdsd.d/config-cache/metricsextension", "-Input", "otlp_grpc_prom", "-ConfigOverridesFilePath", "/usr/sbin/me.config")
// Create a file to store the stdoutput
// metricsextension_stdout_file, err := os.Create("metricsextension_stdout.log")
// if err != nil {
// fmt.Printf("Error creating output file for metrics extension: %v\n", err)
// return
// }
// // Create a file to store the stderr
// metricsextension_stderr_file, err := os.Create("metricsextension_stderr.log")
// if err != nil {
// fmt.Printf("Error creating output file for metrics extension: %v\n", err)
// return
// }
// Create pipes to capture stdout and stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Printf("Error creating stdout pipe: %v\n", err)
return
}
stderr, err := cmd.StderrPipe()
if err != nil {
fmt.Printf("Error creating stderr pipe: %v\n", err)
return
}
// Goroutines to copy stdout and stderr to parent process
// Copy output to only stdout & stderr
go copyOutputPipe(stdout, os.Stdout)
go copyOutputPipe(stderr, os.Stderr)
// Copy output to both stdout & stderr and file
// go copyOutputMulti(stdout, os.Stdout, metricsextension_stdout_file)
// go copyOutputMulti(stderr, os.Stderr, metricsextension_stderr_file)
// Copy output only to file
// go copyOutputFile(stdout, metricsextension_stdout_file)
// go copyOutputFile(stderr, metricsextension_stderr_file)
// Start the command
err = cmd.Start()
if err != nil {
fmt.Printf("Error starting MetricsExtension: %v\n", err)
return
}
}
func StartMA() {
osType := os.Getenv("OS_TYPE")
var cmd *exec.Cmd
switch osType {
case "linux":
fmt.Println("Should never reach here")
case "windows":
cmd = exec.Command("C:\\opt\\genevamonitoringagent\\genevamonitoringagent\\Monitoring\\Agent\\MonAgentLauncher.exe", "-useenv")
// On Windows, stderr redirection is not needed as `cmd.Start()` handles it internally
}
// Start the command
err := cmd.Start()
if err != nil {
fmt.Printf("Error starting mdsd/MonAgentLauncher: %v\n", err)
return
}
fmt.Printf("%s process started successfully.\n", cmd.Path)
}
// StartMdsdForOverlay starts the mdsd process based on the OS
func StartMdsdForOverlay() {
osType := os.Getenv("OS_TYPE")
var cmd *exec.Cmd
switch osType {
case "linux":
mdsdLog := os.Getenv("MDSD_LOG")
if mdsdLog == "" {
fmt.Println("MDSD_LOG environment variable is not set")
return
}
cmd = exec.Command("/usr/sbin/mdsd", "-a", "-A", "-e", mdsdLog+"/mdsd.err", "-w", mdsdLog+"/mdsd.warn", "-o", mdsdLog+"/mdsd.info", "-q", mdsdLog+"/mdsd.qos")
// Redirect stderr to /dev/null
cmd.Stderr = nil
case "windows":
cmd = exec.Command("C:\\opt\\genevamonitoringagent\\genevamonitoringagent\\Monitoring\\Agent\\MonAgentLauncher.exe", "-useenv")
// On Windows, stderr redirection is not needed as `cmd.Start()` handles it internally
}
// Start the command
err := cmd.Start()
if err != nil {
fmt.Printf("Error starting mdsd/MonAgentLauncher: %v\n", err)
return
}
fmt.Printf("%s process started successfully.\n", cmd.Path)
}
func StartMdsdForUnderlay() {
cmd := exec.Command("/usr/sbin/mdsd", "-a", "-A", "-D")
// // Create a file to store the stdoutput
// mdsd_stdout_file, err := os.Create("mdsd_stdout.log")
// if err != nil {
// fmt.Printf("Error creating output file for mdsd: %v\n", err)
// return
// }
// // Create a file to store the stderr
// mdsd_stderr_file, err := os.Create("mdsd_stderr.log")
// if err != nil {
// fmt.Printf("Error creating output file for mdsd: %v\n", err)
// return
// }
// Create pipes to capture stdout and stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Printf("Error creating stdout pipe: %v\n", err)
return
}
stderr, err := cmd.StderrPipe()
if err != nil {
fmt.Printf("Error creating stderr pipe: %v\n", err)
return
}
// Goroutines to copy stdout and stderr to parent process
// Copy output to only stdout and stderr
go copyOutputPipe(stdout, os.Stdout)
go copyOutputPipe(stderr, os.Stderr)
// Copy output to both stdout and file
// go copyOutputMulti(stdout, os.Stdout, mdsd_stdout_file)
// go copyOutputMulti(stderr, os.Stderr, mdsd_stderr_file)
// Copy output only to file
// go copyOutputFile(stdout, mdsd_stdout_file)
// go copyOutputFile(stderr, mdsd_stderr_file)
// Start the command
err = cmd.Start()
if err != nil {
fmt.Printf("Error starting mdsd: %v\n", err)
return
}
}
func StartCronDaemon() {
cmd := exec.Command("/usr/sbin/crond", "-n", "-s")
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
}
func WaitForTokenAdapter(ccpMetricsEnabled string) {
tokenAdapterWaitSecs := 60
if ccpMetricsEnabled == "true" {
tokenAdapterWaitSecs = 20
}
waitedSecsSoFar := 1
var resp *http.Response
var err error
client := &http.Client{Timeout: time.Duration(2) * time.Second}
req, err := http.NewRequest("GET", "http://localhost:9999/healthz", nil)
if err != nil {
log.Printf("Unable to create http request for the healthz endpoint")
return
}
for {
if waitedSecsSoFar > tokenAdapterWaitSecs {
if resp, err = client.Do(req); err != nil {
log.Printf("giving up waiting for token adapter to become healthy after %d secs\n", waitedSecsSoFar)
log.Printf("export tokenadapterUnhealthyAfterSecs=%d\n", waitedSecsSoFar)
break
}
} else {
log.Printf("checking health of token adapter after %d secs\n", waitedSecsSoFar)
resp, err = client.Do(req)
if err == nil && resp.StatusCode == http.StatusOK {
log.Printf("found token adapter to be healthy after %d secs\n", waitedSecsSoFar)
log.Printf("export tokenadapterHealthyAfterSecs=%d\n", waitedSecsSoFar)
break
}
}
time.Sleep(1 * time.Second)
waitedSecsSoFar++
}
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
}
func StartFluentBit(fluentBitConfigFile string) {
fmt.Println("Starting fluent-bit")
if os.Getenv("OS_TYPE") == "linux" {
if err := os.Mkdir("/opt/microsoft/fluent-bit", 0755); err != nil && !os.IsExist(err) {
log.Fatalf("Error creating directory: %v\n", err)
}
logFile, err := os.Create("/opt/microsoft/fluent-bit/fluent-bit-out-appinsights-runtime.log")
if err != nil {
log.Fatalf("Error creating log file: %v\n", err)
}
defer logFile.Close()
fluentBitCmd := exec.Command("fluent-bit", "-c", fluentBitConfigFile, "-e", "/opt/fluent-bit/bin/out_appinsights.so")
fluentBitCmd.Stdout = os.Stdout
fluentBitCmd.Stderr = os.Stderr
if err := fluentBitCmd.Start(); err != nil {
log.Fatalf("Error starting fluent-bit: %v\n", err)
}
} else {
fluentBitCmd := exec.Command("C:\\opt\\fluent-bit\\bin\\fluent-bit.exe", "-c", "C:\\opt\\fluent-bit\\fluent-bit-windows.conf", "-e", "C:\\opt\\fluent-bit\\bin\\out_appinsights.so")
fluentBitCmd.Stdout = os.Stdout
fluentBitCmd.Stderr = os.Stderr
if err := fluentBitCmd.Start(); err != nil {
log.Fatalf("Error starting fluent-bit: %v\n", err)
}
// Run fluent-bit as a background process
go func() {
if err := fluentBitCmd.Wait(); err != nil {
log.Printf("Fluent-bit exited with error: %v\n", err)
}
}()
}
}