cmd/hub/metrics/meter.go (168 lines of code) (raw):
// Copyright (c) 2022 EPAM Systems, Inc.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package metrics
import (
"errors"
"fmt"
"io"
"log"
"os"
"os/exec"
"strings"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/epam/hubctl/cmd/hub/config"
"github.com/epam/hubctl/cmd/hub/filecache"
"github.com/epam/hubctl/cmd/hub/util"
)
var (
httpClient = util.RobustHttpClient(0, false)
cachedConfig *filecache.Metrics
)
func MeterCommand(cmd *cobra.Command, connectStdin bool) io.WriteCloser {
if ddKey == "" && MetricsServiceKey == "" {
return nil
}
stdin, err := meterCommand(cmd, connectStdin)
if err != nil {
util.Warn("Unable to send usage metrics: %v", err)
}
return stdin
}
func meterCommand(cmd *cobra.Command, connectStdin bool) (io.WriteCloser, error) {
enabled, _, err := meteringConfig()
if err != nil {
return nil, fmt.Errorf("Unable to load metrics config: %v", err)
}
if !enabled {
if config.Trace {
log.Print("Usage metering is not enabled")
}
return nil, nil
}
bin, err := os.Executable()
if err != nil {
return nil, fmt.Errorf("Unable to determine path to Hub CTL executable: %v", err)
}
args := []string{"hub", "util", "metrics"}
if connectStdin {
args = append(args, "--tags-stdin")
}
args = append(args, commandStr(cmd))
hub := exec.Cmd{
Path: bin,
Args: args,
}
if config.Trace {
hub.Stdout = os.Stdout
hub.Stderr = os.Stderr
}
var stdin io.WriteCloser
if connectStdin {
stdin, err = hub.StdinPipe()
if err != nil {
return nil, err
}
}
err = hub.Start()
if err != nil {
if stdin != nil {
stdin.Close()
}
return nil, err
}
go hub.Wait()
return stdin, nil
}
func PutMetrics(cmd string, tags []string) {
err := putMetrics(cmd, tags)
if err != nil {
log.Fatalf("Unable to send usage metrics: %v", err)
}
}
func putMetrics(cmd string, tags []string) error {
enabled, host, err := meteringConfig()
if err != nil {
return fmt.Errorf("Unable to load metrics config: %v", err)
}
if config.Debug && !enabled && (ddKey != "" || MetricsServiceKey != "") {
log.Print("Usage metering is not enabled; continuing as requested")
}
var err1, err2 error
if MetricsServiceKey != "" {
err1 = putMetricsServiceMetric(cmd, host, tags)
}
if ddKey != "" {
err2 = putDDMetric(cmd, host, tags)
}
if err1 != nil || err2 != nil {
err = errors.New(util.Errors2(err1, err2))
}
return err
}
func commandStr(cmd *cobra.Command) string {
var parts []string
for cmd2 := cmd; cmd2 != nil; cmd2 = cmd2.Parent() {
use := cmd2.Use
i := strings.Index(use, " ")
if i > 0 {
use = use[:i]
}
parts = append(parts, use)
}
return strings.Join(util.Reverse(parts), "-")
}
func meteringConfig() (bool, string, error) {
var cache *filecache.FileCache
conf := cachedConfig
if conf == nil {
file, cache2, err := filecache.ReadCache(os.O_RDONLY)
cache = cache2
if err != nil {
return false, "", err
}
if file != nil {
file.Close()
}
if cache != nil {
conf = &cache.Metrics
}
}
// metrics disabled
if conf != nil && conf.Disabled {
cachedConfig = conf
return !conf.Disabled, "", nil
}
if conf == nil {
conf = &filecache.Metrics{}
}
// generate and save host uuid if interactive session
var writeErr error
if conf.Host == nil && (config.Tty && !config.TtyForced) {
u, err := uuid.NewRandom()
if err != nil {
util.Warn("Unable to generate host random v4 UUID: %v", err)
}
uuidStr := u.String()
conf.Host = &uuidStr
file, cache, err := filecache.ReadCache(os.O_RDWR | os.O_CREATE)
if err != nil {
return !conf.Disabled, "", err
}
if file == nil {
return !conf.Disabled, "", errors.New("No cache file created")
}
defer file.Close()
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return !conf.Disabled, "", err
}
if cache == nil {
cache = &filecache.FileCache{}
}
cache.Metrics = *conf
writeErr = filecache.WriteCache(file, cache)
}
cachedConfig = conf
host := ""
if conf.Host != nil {
host = *conf.Host
}
return !conf.Disabled, host, writeErr
}