github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/ssa/ssa.go (about)

     1  /*
     2  Copyright 2023.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package ssa
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"math"
    23  	"net/http"
    24  	"os"
    25  	"runtime"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/segmentio/analytics-go/v3"
    31  	"github.com/shirou/gopsutil/v3/cpu"
    32  	"github.com/shirou/gopsutil/v3/mem"
    33  	"github.com/siglens/siglens/pkg/config"
    34  	"github.com/siglens/siglens/pkg/localnodeid"
    35  	segwriter "github.com/siglens/siglens/pkg/segment/writer"
    36  	"github.com/siglens/siglens/pkg/usageStats"
    37  	"github.com/siglens/siglens/pkg/utils"
    38  	"github.com/siglens/siglens/pkg/virtualtable"
    39  	log "github.com/sirupsen/logrus"
    40  )
    41  
    42  const kubernetes = "kubernetes"
    43  const docker = "docker"
    44  const binary = "binary"
    45  
    46  var client analytics.Client = nil
    47  var ssaStarted = false
    48  var segmentKey string = "BPDjnefPV0Jc2BRGdGh7CQTnykYKbD8c"
    49  var userId = ""
    50  var IPAddressInfo IPAddressDetails
    51  var source = "computerID"
    52  
    53  type IPAddressDetails struct {
    54  	IP        string  `json:"ip"`
    55  	City      string  `json:"city"`
    56  	Region    string  `json:"region"`
    57  	Country   string  `json:"country"`
    58  	Loc       string  `json:"loc"`
    59  	Latitude  float64 `json:"-"`
    60  	Longitude float64 `json:"-"`
    61  	Timezone  string  `json:"timezone"`
    62  }
    63  
    64  type silentLogger struct {
    65  }
    66  
    67  func (sl *silentLogger) Logf(format string, args ...interface{}) {
    68  }
    69  
    70  func (sl *silentLogger) Errorf(format string, args ...interface{}) {
    71  }
    72  func FetchIPAddressDetails() (IPAddressDetails, error) {
    73  	var details IPAddressDetails
    74  	resp, err := http.Get("https://ipinfo.io")
    75  	if err != nil {
    76  		log.Errorf("Failed to fetch IP address details: %v", err)
    77  		return details, err
    78  	}
    79  	defer resp.Body.Close()
    80  
    81  	if err := json.NewDecoder(resp.Body).Decode(&details); err != nil {
    82  		log.Errorf("Failed to decode IP address details: %v", err)
    83  		return details, err
    84  	}
    85  
    86  	// Parse latitude and longitude from Loc
    87  	locParts := strings.Split(details.Loc, ",")
    88  	if len(locParts) == 2 {
    89  		if lat, err := strconv.ParseFloat(locParts[0], 64); err == nil {
    90  			details.Latitude = lat
    91  		} else {
    92  			log.Errorf("Failed to parse latitude: %v", err)
    93  		}
    94  		if lon, err := strconv.ParseFloat(locParts[1], 64); err == nil {
    95  			details.Longitude = lon
    96  		} else {
    97  			log.Errorf("Failed to parse longitude: %v", err)
    98  		}
    99  	} else {
   100  		log.Errorf("Failed to parse location: %v", details.Loc)
   101  	}
   102  
   103  	log.Infof("Successfully fetched and decoded IP address details")
   104  
   105  	return details, nil
   106  }
   107  func InitSsa() {
   108  
   109  	currClient, err := analytics.NewWithConfig(segmentKey,
   110  		analytics.Config{
   111  			Verbose: false,
   112  			Logger:  &silentLogger{},
   113  		},
   114  	)
   115  
   116  	if err != nil {
   117  		log.Errorf("Error initializing ssa: %v", err)
   118  		return
   119  	}
   120  	ipDetails, err := FetchIPAddressDetails()
   121  	if err != nil {
   122  		log.Fatalf("Failed to fetch IP address details: %v", err)
   123  	}
   124  
   125  	IPAddressInfo = ipDetails
   126  	client = currClient
   127  	go waitForInitialEvent()
   128  }
   129  
   130  func waitForInitialEvent() {
   131  	time.Sleep(2 * time.Minute)
   132  
   133  	traits := analytics.NewTraits()
   134  	props := analytics.NewProperties()
   135  
   136  	// Initialize computer-specific identifier
   137  	if userId = os.Getenv("CSI"); userId != "" {
   138  		source = "CSI"
   139  	} else {
   140  		var err error
   141  		userId, err = utils.GetSpecificIdentifier()
   142  		if err != nil {
   143  			log.Errorf("waitForInitialEvent: %v", err)
   144  			userId = "unknown"
   145  		}
   146  	}
   147  	baseInfo := getBaseInfo()
   148  	for k, v := range baseInfo {
   149  		traits.Set(k, v)
   150  		props.Set(k, v)
   151  	}
   152  	props.Set("id_source", source)
   153  	_ = client.Enqueue(analytics.Identify{
   154  		UserId: userId,
   155  		Traits: traits,
   156  	})
   157  	if localnodeid.IsInitServer() {
   158  		_ = client.Enqueue(analytics.Track{
   159  			Event:      "server startup",
   160  			UserId:     userId,
   161  			Properties: props,
   162  		})
   163  	} else {
   164  		_ = client.Enqueue(analytics.Track{
   165  			Event:      "server restart",
   166  			UserId:     userId,
   167  			Properties: props,
   168  		})
   169  	}
   170  	go startSsa()
   171  }
   172  
   173  func StopSsa() {
   174  
   175  	if client == nil || !ssaStarted {
   176  		return
   177  	}
   178  	props := make(map[string]interface{})
   179  	props["runtime_os"] = runtime.GOOS
   180  	props["runtime_arch"] = runtime.GOARCH
   181  	populateDeploymentSsa(props)
   182  	populateIngestSsa(props)
   183  	populateQuerySsa(props)
   184  	_ = client.Enqueue(analytics.Track{
   185  		Event:      "server shutdown",
   186  		UserId:     userId,
   187  		Properties: props,
   188  	})
   189  	err := client.Close()
   190  	if err != nil {
   191  		log.Debugf("Failed to stop ssa module! Error: %v", err)
   192  	}
   193  }
   194  
   195  func startSsa() {
   196  	ssaStarted = true
   197  	for {
   198  		flushSsa()
   199  		time.Sleep(time.Hour * 3)
   200  	}
   201  }
   202  
   203  func flushSsa() {
   204  
   205  	allSsa := getSsa()
   206  	props := analytics.NewProperties()
   207  	for k, v := range allSsa {
   208  		props.Set(k, v)
   209  	}
   210  	props.Set("runtime_os", runtime.GOOS)
   211  	props.Set("runtime_arch", runtime.GOARCH)
   212  	props.Set("id_source", source)
   213  	_ = client.Enqueue(analytics.Track{
   214  		Event:      "server status",
   215  		UserId:     userId,
   216  		Properties: props,
   217  	})
   218  }
   219  
   220  func getSsa() map[string]interface{} {
   221  
   222  	ssa := make(map[string]interface{})
   223  	populateDeploymentSsa(ssa)
   224  	populateIngestSsa(ssa)
   225  	populateQuerySsa(ssa)
   226  	return ssa
   227  }
   228  
   229  func getBaseInfo() map[string]interface{} {
   230  
   231  	m := make(map[string]interface{})
   232  	mem, _ := mem.VirtualMemory()
   233  	cpuCount, _ := cpu.Counts(true)
   234  	zone, _ := time.Now().Local().Zone()
   235  
   236  	m["runtime_os"] = runtime.GOOS
   237  	m["runtime_arch"] = runtime.GOARCH
   238  	m["time_zone"] = zone
   239  	m["cpu_count"] = cpuCount
   240  	m["total_memory_gb"] = mem.Total / (1000 * 1000 * 1000)
   241  	m["company_name"] = "OSS"
   242  	m["ip"] = IPAddressInfo.IP
   243  	m["city"] = IPAddressInfo.City
   244  	m["region"] = IPAddressInfo.Region
   245  	m["country"] = IPAddressInfo.Country
   246  	return m
   247  }
   248  
   249  func populateDeploymentSsa(m map[string]interface{}) {
   250  	m["uptime_minutes"] = math.Round(time.Since(utils.GetServerStartTime()).Minutes())
   251  	m["retention_hours"] = config.GetRetentionHours()
   252  	m["company_name"] = "OSS"
   253  	m["version"] = config.SigLensVersion
   254  	m["deployment_type"] = getDeploymentType()
   255  	m["ip"] = IPAddressInfo.IP
   256  	m["city"] = IPAddressInfo.City
   257  	m["region"] = IPAddressInfo.Region
   258  	m["country"] = IPAddressInfo.Country
   259  }
   260  
   261  func populateIngestSsa(m map[string]interface{}) {
   262  	allVirtualTableNames, _ := virtualtable.GetVirtualTableNames(0)
   263  
   264  	totalEventCount := uint64(0)
   265  	totalOnDiskBytes := uint64(0)
   266  	totalIncomingBytes := uint64(0)
   267  
   268  	largestIndexEventCount := uint64(0)
   269  	for indexName := range allVirtualTableNames {
   270  		if indexName == "" {
   271  			log.Debugf("populateIngestSsa: one of nil indexName=%v", indexName)
   272  			continue
   273  		}
   274  		bytesReceivedCount, eventCount, onDiskBytesCount := segwriter.GetVTableCounts(indexName, 0)
   275  		unrotatedBytesCount, unrotatedEventCount, unrotatedOnDiskBytesCount := segwriter.GetUnrotatedVTableCounts(indexName, 0)
   276  		bytesReceivedCount += unrotatedBytesCount
   277  		eventCount += unrotatedEventCount
   278  		onDiskBytesCount += unrotatedOnDiskBytesCount
   279  		totalEventCount += uint64(eventCount)
   280  		totalOnDiskBytes += onDiskBytesCount
   281  		totalIncomingBytes += bytesReceivedCount
   282  
   283  		if totalEventCount > largestIndexEventCount {
   284  			largestIndexEventCount = totalEventCount
   285  		}
   286  	}
   287  	m["total_event_count"] = totalEventCount
   288  	m["total_on_disk_bytes"] = totalOnDiskBytes
   289  	m["total_incoming_bytes"] = totalIncomingBytes
   290  	m["total_table_count"] = len(allVirtualTableNames)
   291  	m["largest_index_event_count"] = largestIndexEventCount
   292  	m["ip"] = IPAddressInfo.IP
   293  	m["city"] = IPAddressInfo.City
   294  	m["region"] = IPAddressInfo.Region
   295  	m["country"] = IPAddressInfo.Country
   296  }
   297  
   298  func populateQuerySsa(m map[string]interface{}) {
   299  	queryCount, totalResponseTime, querieSinceInstall := usageStats.GetQueryStats(0)
   300  	m["num_queries"] = queryCount
   301  	m["queries_since_install"] = querieSinceInstall
   302  	m["ip"] = IPAddressInfo.IP
   303  	m["city"] = IPAddressInfo.City
   304  	m["region"] = IPAddressInfo.Region
   305  	m["country"] = IPAddressInfo.Country
   306  	if queryCount > 1 {
   307  		m["avg_query_latency_ms"] = fmt.Sprintf("%v", utils.ToFixed(totalResponseTime/float64(queryCount), 3)) + " ms"
   308  	} else {
   309  		m["avg_query_latency_ms"] = fmt.Sprintf("%v", utils.ToFixed(totalResponseTime, 3)) + " ms"
   310  	}
   311  }
   312  
   313  func getDeploymentType() string {
   314  	if _, exists := os.LookupEnv("KUBERNETES_SERVICE_HOST"); exists {
   315  		return kubernetes
   316  	}
   317  
   318  	if _, exists := os.LookupEnv("DOCKER_HOST"); exists {
   319  		return docker
   320  	}
   321  
   322  	return binary
   323  }