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 }