go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/uptime/unix.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package uptime
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/rs/zerolog/log"
    15  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    16  )
    17  
    18  var UnixUptimeRegex = regexp.MustCompile(`^.*up[\s]*(?:\s*(\d+)\s(day[s]*),)*(?:\s*(\d+)\s(min[s]*),)*(?:\s+([\d:]+),\s)*\s*(?:(\d+)\suser[s]*,\s)*\s*load\s+average[s]*:\s+(\d+[\.,]\d+)[,\s]+(\d+[\.,]\d+)[,\s]+(\d+[\.,]\d+)\s*$`)
    19  
    20  type UnixUptimeResult struct {
    21  	Duration           int64
    22  	Users              int
    23  	LoadOneMinute      float64
    24  	LoadFiveMinutes    float64
    25  	LoadFifteenMinutes float64
    26  }
    27  
    28  func unixDuration(date, measure string) (int64, error) {
    29  	// calculate the time x * days / minutes + hours ( m[1]*m[2] + m[3])
    30  	duration, err := strconv.ParseInt(date, 10, 64)
    31  	if err != nil {
    32  		return 0, err
    33  	}
    34  
    35  	switch measure {
    36  	case "day":
    37  		fallthrough
    38  	case "days":
    39  		duration = duration * 24 * int64(time.Hour)
    40  	case "min":
    41  		fallthrough
    42  	case "mins":
    43  		duration = duration * int64(time.Minute)
    44  	}
    45  	return duration, nil
    46  }
    47  
    48  func ParseUnixUptime(uptime string) (*UnixUptimeResult, error) {
    49  	log.Debug().Str("uptime", uptime).Msg("parse")
    50  	m := UnixUptimeRegex.FindStringSubmatch(uptime)
    51  
    52  	if len(m) != 10 {
    53  		return nil, fmt.Errorf("could not parse uptime: %s", uptime)
    54  	}
    55  
    56  	var duration int64
    57  	var err error
    58  
    59  	// parse days
    60  	if len(m[2]) > 0 {
    61  		unixDuration, err := unixDuration(m[1], m[2])
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  		duration = duration + unixDuration
    66  	}
    67  
    68  	// parse mins
    69  	if len(m[4]) > 0 {
    70  		unixDuration, err := unixDuration(m[3], m[4])
    71  		if err != nil {
    72  			return nil, err
    73  		}
    74  		duration = duration + unixDuration
    75  	}
    76  
    77  	// add optional hours
    78  	if len(m[5]) > 0 {
    79  		hours := strings.Split(m[5], ":")
    80  		if len(hours) == 2 {
    81  			// log.Debug().Msg("parse hour")
    82  			hh, err := strconv.ParseInt(hours[0], 10, 64)
    83  			if err != nil {
    84  				return nil, err
    85  			}
    86  
    87  			// log.Debug().Msg("parse minutes")
    88  			mm, err := strconv.ParseInt(hours[1], 10, 64)
    89  			if err != nil {
    90  				return nil, err
    91  			}
    92  
    93  			duration = duration + hh*int64(time.Hour) + mm*int64(time.Minute)
    94  		} else {
    95  			return nil, fmt.Errorf("could not parse uptime hours: %s", uptime)
    96  		}
    97  	}
    98  
    99  	// users is optional and is not returned on alpine
   100  	users := 0
   101  	if len(m[6]) > 0 {
   102  		users, err = strconv.Atoi(m[6])
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  	}
   107  
   108  	loadOneMinute, err := strconv.ParseFloat(strings.Replace(m[7], ",", ".", 1), 64)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	loadFiveMinutes, err := strconv.ParseFloat(strings.Replace(m[8], ",", ".", 1), 64)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	loadFifteenMinutes, err := strconv.ParseFloat(strings.Replace(m[9], ",", ".", 1), 64)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	return &UnixUptimeResult{
   124  		Duration:           duration,
   125  		Users:              users,
   126  		LoadOneMinute:      loadOneMinute,
   127  		LoadFiveMinutes:    loadFiveMinutes,
   128  		LoadFifteenMinutes: loadFifteenMinutes,
   129  	}, nil
   130  }
   131  
   132  type Unix struct {
   133  	conn shared.Connection
   134  }
   135  
   136  func (s *Unix) Name() string {
   137  	return "Unix Uptime"
   138  }
   139  
   140  func (s *Unix) Duration() (time.Duration, error) {
   141  	cmd, err := s.conn.RunCommand("uptime")
   142  	if err != nil {
   143  		return 0, err
   144  	}
   145  
   146  	ut, err := s.parse(cmd.Stdout)
   147  	if err != nil {
   148  		return 0, err
   149  	}
   150  
   151  	return time.Duration(ut.Duration), nil
   152  }
   153  
   154  func (s *Unix) parse(r io.Reader) (*UnixUptimeResult, error) {
   155  	content, err := io.ReadAll(r)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	return ParseUnixUptime(string(content))
   161  }