github.com/Axway/agent-sdk@v1.1.101/pkg/transaction/metric/metricscollector.go (about) 1 package metric 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "strconv" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/golang-jwt/jwt" 13 "github.com/google/uuid" 14 "github.com/rcrowley/go-metrics" 15 16 "github.com/Axway/agent-sdk/pkg/agent" 17 "github.com/Axway/agent-sdk/pkg/agent/cache" 18 v1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1" 19 catalog "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/catalog/v1alpha1" 20 management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1" 21 "github.com/Axway/agent-sdk/pkg/cmd" 22 "github.com/Axway/agent-sdk/pkg/config" 23 "github.com/Axway/agent-sdk/pkg/jobs" 24 "github.com/Axway/agent-sdk/pkg/traceability" 25 "github.com/Axway/agent-sdk/pkg/transaction/models" 26 transutil "github.com/Axway/agent-sdk/pkg/transaction/util" 27 "github.com/Axway/agent-sdk/pkg/util" 28 "github.com/Axway/agent-sdk/pkg/util/healthcheck" 29 "github.com/Axway/agent-sdk/pkg/util/log" 30 ) 31 32 const ( 33 startTimestampStr = "start-timestamp" 34 endTimestampStr = "end-timestamp" 35 eventTypeStr = "event-type" 36 usageStr = "usage" 37 metricStr = "metric" 38 volumeStr = "volume" 39 countStr = "count" 40 ) 41 42 var exitMetricInit = false 43 var exitMutex = &sync.RWMutex{} 44 45 func ExitMetricInit() { 46 exitMutex.Lock() 47 defer exitMutex.Unlock() 48 exitMetricInit = true 49 } 50 51 // Collector - interface for collecting metrics 52 type Collector interface { 53 InitializeBatch() 54 AddMetric(apiDetails models.APIDetails, statusCode string, duration, bytes int64, appName string) 55 AddMetricDetail(metricDetail Detail) 56 AddAPIMetricDetail(metric MetricDetail) 57 AddAPIMetric(apiMetric *APIMetric) 58 Publish() 59 ShutdownPublish() 60 } 61 62 // collector - collects the metrics for transactions events 63 type collector struct { 64 jobs.Job 65 usageStartTime time.Time 66 usageEndTime time.Time 67 metricStartTime time.Time 68 metricEndTime time.Time 69 orgGUID string 70 lock *sync.Mutex 71 batchLock *sync.Mutex 72 registry metrics.Registry 73 metricBatch *EventBatch 74 metricMap map[string]map[string]map[string]map[string]*APIMetric 75 metricMapLock *sync.Mutex 76 publishItemQueue []publishQueueItem 77 jobID string 78 usagePublisher *usagePublisher 79 storage storageCache 80 reports *usageReportCache 81 metricConfig config.MetricReportingConfig 82 usageConfig config.UsageReportingConfig 83 logger log.FieldLogger 84 metricLogger log.FieldLogger 85 } 86 87 type publishQueueItem interface { 88 GetEvent() interface{} 89 GetUsageMetric() interface{} 90 GetVolumeMetric() interface{} 91 } 92 93 type usageEventPublishItem interface { 94 publishQueueItem 95 } 96 97 type usageEventQueueItem struct { 98 usageEventPublishItem 99 event UsageEvent 100 usageMetric metrics.Counter 101 volumeMetric metrics.Counter 102 } 103 104 func init() { 105 go func() { 106 // Wait for the datadir to be set and exist 107 dataDir := "" 108 _, err := os.Stat(dataDir) 109 for dataDir == "" || os.IsNotExist(err) { 110 time.Sleep(time.Millisecond * 50) 111 exitMutex.RLock() 112 if exitMetricInit { 113 exitMutex.RUnlock() 114 return 115 } 116 exitMutex.RUnlock() 117 118 dataDir = traceability.GetDataDirPath() 119 _, err = os.Stat(dataDir) 120 } 121 GetMetricCollector() 122 }() 123 } 124 125 func (qi *usageEventQueueItem) GetEvent() interface{} { 126 return qi.event 127 } 128 129 func (qi *usageEventQueueItem) GetUsageMetric() interface{} { 130 return qi.usageMetric 131 } 132 133 func (qi *usageEventQueueItem) GetVolumeMetric() interface{} { 134 return qi.volumeMetric 135 } 136 137 var globalMetricCollector Collector 138 139 // GetMetricCollector - Create metric collector 140 func GetMetricCollector() Collector { 141 // There are beat params on execution that doesn't require central config to be instantiated 142 if agent.GetCentralConfig() == nil { 143 // if this is the case, check central config and if not instantiated, return nil 144 return nil 145 } 146 147 if globalMetricCollector == nil && util.IsNotTest() { 148 globalMetricCollector = createMetricCollector() 149 } 150 151 return globalMetricCollector 152 } 153 154 func createMetricCollector() Collector { 155 logger := log.NewFieldLogger(). 156 WithPackage("sdk.transaction.metric"). 157 WithComponent("collector") 158 metricCollector := &collector{ 159 // Set the initial start time to be minimum 1m behind, so that the job can generate valid event 160 // if any usage event are to be generated on startup 161 usageStartTime: now().Add(-1 * time.Minute), 162 metricStartTime: now().Add(-1 * time.Minute), 163 lock: &sync.Mutex{}, 164 batchLock: &sync.Mutex{}, 165 metricMapLock: &sync.Mutex{}, 166 registry: metrics.NewRegistry(), 167 metricMap: make(map[string]map[string]map[string]map[string]*APIMetric), 168 publishItemQueue: make([]publishQueueItem, 0), 169 metricConfig: agent.GetCentralConfig().GetMetricReportingConfig(), 170 usageConfig: agent.GetCentralConfig().GetUsageReportingConfig(), 171 logger: logger, 172 metricLogger: log.NewMetricFieldLogger(), 173 } 174 175 // Create and initialize the storage cache for usage/metric and offline report cache by loading from disk 176 metricCollector.storage = newStorageCache(metricCollector) 177 metricCollector.storage.initialize() 178 metricCollector.reports = newReportCache() 179 metricCollector.usagePublisher = newUsagePublisher(metricCollector.storage, metricCollector.reports) 180 181 if util.IsNotTest() { 182 var err error 183 if !metricCollector.usageConfig.IsOfflineMode() { 184 metricCollector.jobID, err = jobs.RegisterScheduledJobWithName(metricCollector, metricCollector.metricConfig.GetSchedule(), "Metric Collector") 185 } else { 186 metricCollector.jobID, err = jobs.RegisterScheduledJobWithName(metricCollector, metricCollector.usageConfig.GetOfflineSchedule(), "Metric Collector") 187 } 188 if err != nil { 189 panic(err) 190 } 191 } 192 193 return metricCollector 194 } 195 196 // Status - returns the status of the metric collector 197 func (c *collector) Status() error { 198 return nil 199 } 200 201 // Ready - indicates that the collector job is ready to process 202 func (c *collector) Ready() bool { 203 // Wait until any existing offline reports are saved 204 if c.usageConfig.IsOfflineMode() && !c.usagePublisher.isReady() { 205 return false 206 } 207 return agent.GetCentralConfig().GetEnvironmentID() != "" 208 } 209 210 // Execute - process the metric collection and generation of usage/metric event 211 func (c *collector) Execute() error { 212 c.lock.Lock() 213 defer c.lock.Unlock() 214 215 if !c.usagePublisher.offline && healthcheck.GetStatus(traceability.HealthCheckEndpoint) != healthcheck.OK { 216 c.logger.Warn("traceability is not connected, can not publish metrics at this time") 217 return nil 218 } 219 220 c.usageEndTime = now() 221 c.metricEndTime = now() 222 c.orgGUID = c.getOrgGUID() 223 224 usageMsg := "updating working usage report file" 225 if !c.usageConfig.IsOfflineMode() { 226 usageMsg = "caching usage event" 227 c.logger. 228 WithField(startTimestampStr, util.ConvertTimeToMillis(c.metricStartTime)). 229 WithField(endTimestampStr, util.ConvertTimeToMillis(c.metricEndTime)). 230 WithField(eventTypeStr, metricStr). 231 Debug("generating metric events") 232 } 233 234 c.logger. 235 WithField(startTimestampStr, util.ConvertTimeToMillis(c.usageStartTime)). 236 WithField(endTimestampStr, util.ConvertTimeToMillis(c.usageEndTime)). 237 WithField(eventTypeStr, usageStr). 238 Debug(usageMsg) 239 240 defer c.cleanup() 241 c.generateEvents() 242 c.publishEvents() 243 return nil 244 } 245 246 func (c *collector) InitializeBatch() { 247 c.lock.Lock() 248 defer c.lock.Unlock() 249 c.metricBatch = NewEventBatch(c) 250 } 251 252 // AddMetric - add metric for API transaction to collection 253 func (c *collector) AddMetric(apiDetails models.APIDetails, statusCode string, duration, bytes int64, appName string) { 254 c.lock.Lock() 255 defer c.lock.Unlock() 256 c.batchLock.Lock() 257 defer c.batchLock.Unlock() 258 c.updateUsage(1) 259 c.updateVolume(bytes) 260 } 261 262 // AddMetricDetail - add metric for API transaction and consumer subscription to collection 263 func (c *collector) AddMetricDetail(metricDetail Detail) { 264 c.AddMetric(metricDetail.APIDetails, metricDetail.StatusCode, metricDetail.Duration, metricDetail.Bytes, metricDetail.APIDetails.Name) 265 c.createOrUpdateMetric(metricDetail) 266 } 267 268 // AddMetricDetailSet - add metric details for several response codes and transactions 269 func (c *collector) AddAPIMetricDetail(detail MetricDetail) { 270 if !c.metricConfig.CanPublish() || c.usageConfig.IsOfflineMode() { 271 return 272 } 273 274 newMetric, _ := c.createMetric(TransactionContext{detail.APIDetails, detail.AppDetails, detail.StatusCode}) 275 // update the new metric with all the necessary details 276 newMetric.Count = detail.Count 277 newMetric.Response = detail.Response 278 newMetric.StartTime = time.UnixMilli(detail.Observation.Start) 279 newMetric.Observation = detail.Observation 280 281 c.AddAPIMetric(newMetric) 282 } 283 284 // AddAPIMetric - add api metric for API transaction 285 func (c *collector) AddAPIMetric(metric *APIMetric) { 286 if metric.EventID == "" { 287 metric.EventID = uuid.NewString() 288 } 289 metric.Status = c.getStatusText(metric.StatusCode) 290 291 v4Event := c.createV4Event(metric.Observation.Start, metric) 292 metricData, _ := json.Marshal(v4Event) 293 pubEvent, err := (&CondorMetricEvent{ 294 Message: string(metricData), 295 Fields: make(map[string]interface{}), 296 Timestamp: v4Event.Data.GetStartTime(), 297 ID: v4Event.ID, 298 }).CreateEvent() 299 if err != nil { 300 return 301 } 302 c.updateUsage(metric.Count) 303 c.metricBatch.AddEventWithoutHistogram(pubEvent) 304 } 305 306 func (c *collector) Publish() { 307 c.metricBatch.Publish() 308 } 309 310 func (c *collector) ShutdownPublish() { 311 c.Execute() 312 c.usagePublisher.Execute() 313 } 314 315 func (c *collector) updateVolume(bytes int64) { 316 if !agent.GetCentralConfig().IsAxwayManaged() { 317 return // no need to update volume for customer managed 318 } 319 transactionVolume := c.getOrRegisterCounter(transactionVolumeMetric) 320 transactionVolume.Inc(bytes) 321 c.storage.updateVolume(transactionVolume.Count()) 322 } 323 324 func (c *collector) updateUsage(count int64) { 325 transactionCount := c.getOrRegisterCounter(transactionCountMetric) 326 transactionCount.Inc(count) 327 c.storage.updateUsage(int(transactionCount.Count())) 328 } 329 330 func (c *collector) createMetric(detail TransactionContext) (*APIMetric, []string) { 331 // Go get the access request and managed app 332 accessRequest, managedApp := c.getAccessRequestAndManagedApp(agent.GetCacheManager(), detail) 333 334 // Update consumer details 335 subRef := v1.Reference{ 336 ID: unknown, 337 Name: unknown, 338 } 339 if accessRequest != nil { 340 accessReqSub := accessRequest.GetReferenceByGVK(catalog.SubscriptionGVK()) 341 if accessReqSub.ID != "" { 342 subRef = accessReqSub 343 } 344 } 345 346 appDetail := c.createAppDetail(managedApp) 347 348 // consumer, subscriptionID, appID, apiID, status 349 histogramKeyParts := []string{"consumer", subRef.ID, appDetail.ID, strings.ReplaceAll(detail.APIDetails.ID, ".", "#"), detail.StatusCode} 350 351 return &APIMetric{ 352 Subscription: c.createSubscriptionDetail(subRef), 353 App: appDetail, 354 Product: c.getProduct(accessRequest, c.logger), 355 API: c.createAPIDetail(detail.APIDetails, accessRequest), 356 AssetResource: c.getAssetResource(accessRequest, c.logger), 357 ProductPlan: c.getProductPlan(accessRequest, c.logger), 358 Quota: c.getQuota(accessRequest, c.logger), 359 StatusCode: detail.StatusCode, 360 Status: c.getStatusText(detail.StatusCode), 361 StartTime: now(), 362 EventID: uuid.NewString(), 363 }, histogramKeyParts 364 } 365 366 func (c *collector) createOrUpdateMetric(detail Detail) *APIMetric { 367 if !c.metricConfig.CanPublish() || c.usageConfig.IsOfflineMode() { 368 return nil // no need to update metrics with publish off 369 } 370 371 metric, keyParts := c.createMetric(TransactionContext{detail.APIDetails, detail.AppDetails, detail.StatusCode}) 372 histogram := c.getOrRegisterHistogram(strings.Join(keyParts, ".")) 373 374 c.metricMapLock.Lock() 375 defer c.metricMapLock.Unlock() 376 if _, ok := c.metricMap[keyParts[1]]; !ok { 377 c.metricMap[keyParts[1]] = make(map[string]map[string]map[string]*APIMetric) 378 } 379 if _, ok := c.metricMap[keyParts[1]][keyParts[2]]; !ok { 380 c.metricMap[keyParts[1]][keyParts[2]] = make(map[string]map[string]*APIMetric) 381 } 382 if _, ok := c.metricMap[keyParts[1]][keyParts[2]][keyParts[3]]; !ok { 383 c.metricMap[keyParts[1]][keyParts[2]][keyParts[3]] = make(map[string]*APIMetric) 384 } 385 if _, ok := c.metricMap[keyParts[1]][keyParts[2]][keyParts[3]][keyParts[4]]; !ok { 386 // First api metric for sub+app+api+statuscode, 387 // setup the start time to be used for reporting metric event 388 c.metricMap[keyParts[1]][keyParts[2]][keyParts[3]][keyParts[4]] = metric 389 } 390 391 histogram.Update(detail.Duration) 392 c.storage.updateMetric(histogram, c.metricMap[keyParts[1]][keyParts[2]][keyParts[3]][keyParts[4]]) 393 return c.metricMap[keyParts[1]][keyParts[2]][keyParts[3]][keyParts[4]] 394 } 395 396 // getAccessRequest - 397 func (c *collector) getAccessRequestAndManagedApp(cacheManager cache.Manager, detail TransactionContext) (*management.AccessRequest, *v1.ResourceInstance) { 398 if detail.AppDetails.Name == "" { 399 return nil, nil 400 } 401 402 c.logger. 403 WithField("apiID", detail.APIDetails.ID). 404 WithField("stage", detail.APIDetails.Stage). 405 Trace("metric collector information") 406 407 // get the managed application 408 managedApp := cacheManager.GetManagedApplicationByName(detail.AppDetails.Name) 409 if managedApp == nil { 410 c.logger. 411 WithField("appName", detail.AppDetails.Name). 412 Trace("could not get managed application by name, return empty API metrics") 413 return nil, nil 414 } 415 c.logger. 416 WithField("appName", detail.AppDetails.Name). 417 WithField("managed-app-name", managedApp.Name). 418 Trace("managed application info") 419 420 // get the access request 421 accessRequest := transutil.GetAccessRequest(cacheManager, managedApp, detail.APIDetails.ID, detail.APIDetails.Stage, detail.APIDetails.Version) 422 if accessRequest == nil { 423 c.logger. 424 Debug("could not get access request, return empty API metrics") 425 return nil, nil 426 } 427 c.logger. 428 WithField("managed-app-name", managedApp.Name). 429 WithField("apiID", detail.APIDetails.ID). 430 WithField("stage", detail.APIDetails.Stage). 431 WithField("access-request-name", accessRequest.Name). 432 Trace("managed application info") 433 434 return accessRequest, managedApp 435 } 436 437 func (c *collector) createSubscriptionDetail(subRef v1.Reference) models.Subscription { 438 detail := models.Subscription{ 439 ID: unknown, 440 Name: unknown, 441 } 442 443 if subRef.ID != "" && subRef.Name != "" { 444 detail.ID = subRef.ID 445 detail.Name = subRef.Name 446 } 447 return detail 448 } 449 450 func (c *collector) createAppDetail(app *v1.ResourceInstance) models.AppDetails { 451 detail := models.AppDetails{ 452 ID: unknown, 453 Name: unknown, 454 } 455 456 if app != nil { 457 detail.ID, detail.Name = c.getConsumerApplication(app) 458 detail.ConsumerOrgID = c.getConsumerOrgID(app) 459 } 460 return detail 461 } 462 463 func (c *collector) createAPIDetail(api models.APIDetails, accessReq *management.AccessRequest) models.APIDetails { 464 detail := models.APIDetails{ 465 ID: api.ID, 466 Name: api.Name, 467 Revision: api.Revision, 468 TeamID: api.TeamID, 469 APIServiceInstance: unknown, 470 } 471 472 if accessReq != nil { 473 detail.APIServiceInstance = accessReq.Spec.ApiServiceInstance 474 } 475 return detail 476 } 477 478 func (c *collector) getAssetResource(accessRequest *management.AccessRequest, log log.FieldLogger) models.AssetResource { 479 // Set default to provider details in case access request or managed apps comes back nil 480 assetResource := models.AssetResource{ 481 ID: unknown, 482 Name: unknown, 483 } 484 485 if accessRequest == nil { 486 log.Trace("access request is nil. Setting default values to unknown") 487 return assetResource 488 } 489 490 assetResourceRef := accessRequest.GetReferenceByGVK(catalog.AssetResourceGVK()) 491 if assetResourceRef.ID == "" || assetResourceRef.Name == "" { 492 log.Trace("could not get asset resource, setting asset resource to unknown") 493 } else { 494 assetResource.ID = assetResourceRef.ID 495 assetResource.Name = assetResourceRef.Name 496 } 497 log.WithField("asset-resource-id", assetResource.ID). 498 WithField("asset-resource-name", assetResource.Name). 499 Trace("asset resource information") 500 501 return assetResource 502 } 503 504 func (c *collector) getProduct(accessRequest *management.AccessRequest, log log.FieldLogger) models.Product { 505 product := models.Product{ 506 ID: unknown, 507 Name: unknown, 508 VersionID: unknown, 509 VersionName: unknown, 510 } 511 512 if accessRequest == nil { 513 log.Trace("access request is nil. Setting default values to unknown") 514 return product 515 } 516 517 productRef := accessRequest.GetReferenceByGVK(catalog.ProductGVK()) 518 if productRef.ID == "" || productRef.Name == "" { 519 log.Trace("could not get product information, setting product to unknown") 520 } else { 521 product.ID = productRef.ID 522 product.Name = productRef.Name 523 } 524 525 productReleaseRef := accessRequest.GetReferenceByGVK(catalog.ProductReleaseGVK()) 526 if productReleaseRef.ID == "" || productReleaseRef.Name == "" { 527 log.Trace("could not get product release information, setting product release to unknown") 528 } else { 529 product.VersionID = productReleaseRef.ID 530 product.VersionName = productReleaseRef.Name 531 } 532 log.WithField("product-id", product.ID). 533 WithField("product-name", product.Name). 534 WithField("product-version-id", product.VersionID). 535 WithField("product-version-name", product.VersionName). 536 Trace("product information") 537 return product 538 539 } 540 541 func (c *collector) getProductPlan(accessRequest *management.AccessRequest, log log.FieldLogger) models.ProductPlan { 542 productPlan := models.ProductPlan{ 543 ID: unknown, 544 } 545 546 if accessRequest == nil { 547 log.Trace("access request is nil. Setting default values to unknown") 548 return productPlan 549 } 550 551 productPlanRef := accessRequest.GetReferenceByGVK(catalog.ProductPlanGVK()) 552 if productPlanRef.ID == "" { 553 log.Debug("could not get product plan ID, setting product plan to unknown") 554 } else { 555 productPlan.ID = productPlanRef.ID 556 } 557 log.WithField("product-plan-id", productPlan.ID). 558 Trace("product plan ID information") 559 560 return productPlan 561 } 562 563 func (c *collector) getQuota(accessRequest *management.AccessRequest, log log.FieldLogger) models.Quota { 564 quota := models.Quota{ 565 ID: unknown, 566 } 567 if accessRequest == nil { 568 log.Trace("access request or managed app is nil. Setting default values to unknown") 569 return quota 570 } 571 quotaRef := accessRequest.GetReferenceByGVK(catalog.QuotaGVK()) 572 if quotaRef.ID == "" { 573 log.Debug("could not get quota ID, setting quota to unknown") 574 } else { 575 quota.ID = quotaRef.ID 576 } 577 log.WithField("quota-id", quota.ID). 578 Trace("quota ID information") 579 580 return quota 581 } 582 583 func (c *collector) cleanup() { 584 c.publishItemQueue = make([]publishQueueItem, 0) 585 } 586 587 func (c *collector) getOrgGUID() string { 588 authToken, _ := agent.GetCentralAuthToken() 589 parser := new(jwt.Parser) 590 parser.SkipClaimsValidation = true 591 592 claims := jwt.MapClaims{} 593 _, _, err := parser.ParseUnverified(authToken, claims) 594 if err != nil { 595 return "" 596 } 597 598 claim, ok := claims["org_guid"] 599 if ok { 600 return claim.(string) 601 } 602 return "" 603 } 604 605 func (c *collector) generateEvents() { 606 if agent.GetCentralConfig().GetEnvironmentID() == "" || cmd.GetBuildDataPlaneType() == "" { 607 c.logger.Warn("Unable to process usage and metric event generation. Please verify the agent config") 608 return 609 } 610 611 c.metricBatch = NewEventBatch(c) 612 c.registry.Each(c.processRegistry) 613 614 if len(c.metricBatch.events) == 0 && !c.usageConfig.IsOfflineMode() { 615 c.logger. 616 WithField(startTimestampStr, util.ConvertTimeToMillis(c.metricStartTime)). 617 WithField(endTimestampStr, util.ConvertTimeToMillis(c.metricEndTime)). 618 WithField(eventTypeStr, metricStr). 619 Info("no metric events generated as no transactions recorded") 620 } 621 622 if c.metricConfig.CanPublish() { 623 err := c.metricBatch.Publish() 624 if err != nil { 625 c.logger.WithError(err).Errorf("could not send metric event, data is kept and will be added to the next trigger interval") 626 } 627 } 628 } 629 630 func (c *collector) processRegistry(name string, metric interface{}) { 631 switch { 632 case name == transactionCountMetric: 633 if c.usageConfig.CanPublish() { 634 c.generateUsageEvent(c.orgGUID) 635 } else { 636 c.logger.Info("Publishing the usage event is turned off") 637 } 638 639 // case transactionVolumeMetric: 640 case name == transactionVolumeMetric: 641 return // skip volume metric as it is handled with Count metric 642 default: 643 c.processMetric(name, metric) 644 } 645 } 646 647 func (c *collector) generateUsageEvent(orgGUID string) { 648 // skip generating a report if no usage when online 649 if c.getOrRegisterCounter(transactionCountMetric).Count() == 0 && !c.usageConfig.IsOfflineMode() { 650 return 651 } 652 653 usageMap := map[string]int64{ 654 fmt.Sprintf("%s.%s", cmd.GetBuildDataPlaneType(), lighthouseTransactions): c.getOrRegisterCounter(transactionCountMetric).Count(), 655 } 656 c.logger. 657 WithField(startTimestampStr, util.ConvertTimeToMillis(c.usageStartTime)). 658 WithField(endTimestampStr, util.ConvertTimeToMillis(c.usageEndTime)). 659 WithField(countStr, c.getOrRegisterCounter(transactionCountMetric).Count()). 660 WithField(eventTypeStr, usageStr). 661 Info("creating usage event for cache") 662 663 if agent.GetCentralConfig().IsAxwayManaged() { 664 usageMap[fmt.Sprintf("%s.%s", cmd.GetBuildDataPlaneType(), lighthouseVolume)] = c.getOrRegisterCounter(transactionVolumeMetric).Count() 665 c.logger. 666 WithField(eventTypeStr, volumeStr). 667 WithField("total-bytes", c.getOrRegisterCounter(transactionVolumeMetric).Count()). 668 WithField(startTimestampStr, util.ConvertTimeToMillis(c.usageStartTime)). 669 WithField(endTimestampStr, util.ConvertTimeToMillis(c.usageEndTime)). 670 Infof("creating volume event for cache") 671 } 672 673 granularity := c.usageConfig.GetReportGranularity() 674 // for offline usage reporting granularity computed with offline schedule 675 if granularity == 0 { 676 granularity = c.metricConfig.GetReportGranularity() 677 } 678 679 reportTime := c.usageStartTime.Format(ISO8601) 680 if c.usageConfig.IsOfflineMode() { 681 reportTime = c.usageEndTime.Add(time.Duration(-1*granularity) * time.Millisecond).Format(ISO8601) 682 } 683 684 usageEvent := UsageEvent{ 685 OrgGUID: orgGUID, 686 EnvID: agent.GetCentralConfig().GetEnvironmentID(), 687 Timestamp: ISO8601Time(c.usageEndTime), 688 SchemaID: c.usageConfig.GetURL() + schemaPath, 689 Granularity: granularity, 690 Report: map[string]UsageReport{ 691 reportTime: { 692 Product: cmd.GetBuildDataPlaneType(), 693 Usage: usageMap, 694 Meta: make(map[string]interface{}), 695 }, 696 }, 697 Meta: map[string]interface{}{ 698 "AgentName": agent.GetCentralConfig().GetAgentName(), 699 "AgentVersion": cmd.BuildVersion, 700 "AgentType": cmd.BuildAgentName, 701 "AgentSDKVersion": cmd.SDKBuildVersion, 702 }, 703 } 704 705 queueItem := &usageEventQueueItem{ 706 event: usageEvent, 707 usageMetric: c.getOrRegisterCounter(transactionCountMetric), 708 volumeMetric: c.getOrRegisterCounter(transactionVolumeMetric), 709 } 710 c.publishItemQueue = append(c.publishItemQueue, queueItem) 711 } 712 713 func (c *collector) processMetric(metricName string, metric interface{}) { 714 c.metricMapLock.Lock() 715 defer c.metricMapLock.Unlock() 716 elements := strings.Split(metricName, ".") 717 if len(elements) == 5 { 718 subscriptionID := elements[1] 719 appID := elements[2] 720 apiID := strings.ReplaceAll(elements[3], "#", ".") 721 statusCode := elements[4] 722 if appMap, ok := c.metricMap[subscriptionID]; ok { 723 if apiMap, ok := appMap[appID]; ok { 724 if statusMap, ok := apiMap[apiID]; ok { 725 if statusDetail, ok := statusMap[statusCode]; ok { 726 statusMetric := (metric.(metrics.Histogram)) 727 c.setMetricsFromHistogram(statusDetail, statusMetric) 728 c.generateMetricEvent(statusMetric, statusDetail) 729 } 730 } 731 } 732 } 733 } 734 } 735 736 func (c *collector) setMetricsFromHistogram(metrics *APIMetric, histogram metrics.Histogram) { 737 metrics.Count = histogram.Count() 738 metrics.Response.Max = histogram.Max() 739 metrics.Response.Min = histogram.Min() 740 metrics.Response.Avg = histogram.Mean() 741 } 742 743 func (c *collector) generateMetricEvent(histogram metrics.Histogram, metric *APIMetric) { 744 if metric.Count == 0 { 745 return 746 } 747 748 metric.Observation.Start = util.ConvertTimeToMillis(c.metricStartTime) 749 metric.Observation.End = util.ConvertTimeToMillis(c.metricEndTime) 750 // Generate app subscription metric 751 c.generateV4Event(histogram, metric) 752 } 753 754 func (c *collector) createV4Event(startTime int64, v4data V4Data) V4Event { 755 return V4Event{ 756 ID: v4data.GetEventID(), 757 Timestamp: startTime, 758 Event: metricEvent, 759 App: c.orgGUID, 760 Version: "4", 761 Distribution: &V4EventDistribution{ 762 Environment: agent.GetCentralConfig().GetEnvironmentID(), 763 Version: "1", 764 }, 765 Data: v4data, 766 } 767 } 768 769 func (c *collector) generateV4Event(histogram metrics.Histogram, v4data V4Data) { 770 generatedEvent := c.createV4Event(c.metricStartTime.UnixMilli(), v4data) 771 c.metricLogger.WithFields(generatedEvent.getLogFields()).Info("generated") 772 AddCondorMetricEventToBatch(generatedEvent, c.metricBatch, histogram) 773 } 774 775 func (c *collector) getOrRegisterCounter(name string) metrics.Counter { 776 counter := c.registry.Get(name) 777 if counter == nil { 778 counter = metrics.NewCounter() 779 c.registry.Register(name, counter) 780 } 781 return counter.(metrics.Counter) 782 } 783 784 func (c *collector) getOrRegisterHistogram(name string) metrics.Histogram { 785 histogram := c.registry.Get(name) 786 if histogram == nil { 787 sampler := metrics.NewUniformSample(2048) 788 histogram = metrics.NewHistogram(sampler) 789 c.registry.Register(name, histogram) 790 } 791 return histogram.(metrics.Histogram) 792 } 793 794 func (c *collector) publishEvents() { 795 if len(c.publishItemQueue) > 0 { 796 defer c.storage.save() 797 798 for _, eventQueueItem := range c.publishItemQueue { 799 err := c.usagePublisher.publishEvent(eventQueueItem.GetEvent()) 800 if err != nil { 801 c.logger. 802 WithError(err). 803 WithField(startTimestampStr, util.ConvertTimeToMillis(c.usageStartTime)). 804 WithField(endTimestampStr, util.ConvertTimeToMillis(c.usageEndTime)). 805 WithField(eventTypeStr, usageStr). 806 Error("failed to add usage report to cache. Current usage report is kept and will be added to the next interval") 807 } else { 808 c.logger. 809 WithField(startTimestampStr, util.ConvertTimeToMillis(c.usageStartTime)). 810 WithField(endTimestampStr, util.ConvertTimeToMillis(c.usageEndTime)). 811 Info("added usage report to cache") 812 c.cleanupCounters(eventQueueItem) 813 } 814 } 815 } 816 } 817 818 func (c *collector) cleanupCounters(eventQueueItem publishQueueItem) { 819 usageEventItem, ok := eventQueueItem.(usageEventPublishItem) 820 if ok { 821 c.cleanupUsageCounter(usageEventItem) 822 } 823 } 824 825 func (c *collector) cleanupUsageCounter(usageEventItem usageEventPublishItem) { 826 itemUsageMetric := usageEventItem.GetUsageMetric() 827 if usage, ok := itemUsageMetric.(metrics.Counter); ok { 828 // Clean up the usage counter and reset the start time to current endTime 829 usage.Clear() 830 itemVolumeMetric := usageEventItem.GetVolumeMetric() 831 if volume, ok := itemVolumeMetric.(metrics.Counter); ok { 832 volume.Clear() 833 } 834 c.usageStartTime = c.usageEndTime 835 c.storage.updateUsage(0) 836 c.storage.updateVolume(0) 837 } 838 } 839 840 func (c *collector) logMetric(msg string, metric *APIMetric) { 841 c.metricLogger.WithField("id", metric.EventID).Info(msg) 842 } 843 844 func (c *collector) cleanupMetricCounter(histogram metrics.Histogram, metric *APIMetric) { 845 c.metricMapLock.Lock() 846 defer c.metricMapLock.Unlock() 847 subID := metric.Subscription.ID 848 appID := metric.App.ID 849 apiID := metric.API.ID 850 statusCode := metric.StatusCode 851 if consumerAppMap, ok := c.metricMap[subID]; ok { 852 if apiMap, ok := consumerAppMap[appID]; ok { 853 if apiStatusMap, ok := apiMap[apiID]; ok { 854 c.storage.removeMetric(apiStatusMap[statusCode]) 855 delete(c.metricMap[subID][appID][apiID], statusCode) 856 histogram.Clear() 857 } 858 if len(c.metricMap[subID][appID][apiID]) == 0 { 859 delete(c.metricMap[subID][appID], apiID) 860 } 861 } 862 if len(c.metricMap[subID][appID]) == 0 { 863 delete(c.metricMap[subID], appID) 864 } 865 } 866 if len(c.metricMap[subID]) == 0 { 867 delete(c.metricMap, subID) 868 } 869 c.logger. 870 WithField(startTimestampStr, util.ConvertTimeToMillis(c.usageStartTime)). 871 WithField(endTimestampStr, util.ConvertTimeToMillis(c.usageEndTime)). 872 WithField("api-name", metric.API.Name). 873 Info("Published metrics report for API") 874 } 875 876 func (c *collector) getStatusText(statusCode string) string { 877 httpStatusCode, _ := strconv.Atoi(statusCode) 878 switch { 879 case httpStatusCode >= 100 && httpStatusCode < 400: 880 return "Success" 881 case httpStatusCode >= 400 && httpStatusCode < 500: 882 return "Failure" 883 default: 884 return "Exception" 885 } 886 } 887 888 func (c *collector) getConsumerOrgID(ri *v1.ResourceInstance) string { 889 if ri == nil { 890 return "" 891 } 892 893 // Lookup Subscription 894 app := &management.ManagedApplication{} 895 app.FromInstance(ri) 896 897 if app.Marketplace.Resource.Owner != nil { 898 return app.Marketplace.Resource.Owner.Organization.ID 899 } 900 return "" 901 } 902 903 func (c *collector) getConsumerApplication(ri *v1.ResourceInstance) (string, string) { 904 if ri == nil { 905 return "", "" 906 } 907 908 for _, ref := range ri.Metadata.References { 909 // get the ID of the Catalog Application 910 if ref.Kind == catalog.ApplicationGVK().Kind { 911 return ref.ID, ref.Name 912 } 913 } 914 915 return ri.Metadata.ID, ri.Name // default to the managed app id 916 }