github.com/newrelic/go-agent@v3.26.0+incompatible/internal/txn_events.go (about) 1 // Copyright 2020 New Relic Corporation. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package internal 5 6 import ( 7 "bytes" 8 "sort" 9 "strings" 10 "time" 11 ) 12 13 // DatastoreExternalTotals contains overview of external and datastore calls 14 // made during a transaction. 15 type DatastoreExternalTotals struct { 16 externalCallCount uint64 17 externalDuration time.Duration 18 datastoreCallCount uint64 19 datastoreDuration time.Duration 20 } 21 22 // WriteJSON prepares JSON in the format expected by the collector. 23 func (e *TxnEvent) WriteJSON(buf *bytes.Buffer) { 24 w := jsonFieldsWriter{buf: buf} 25 buf.WriteByte('[') 26 buf.WriteByte('{') 27 w.stringField("type", "Transaction") 28 w.stringField("name", e.FinalName) 29 w.floatField("timestamp", timeToFloatSeconds(e.Start)) 30 if ApdexNone != e.Zone { 31 w.stringField("nr.apdexPerfZone", e.Zone.label()) 32 } 33 34 w.boolField("error", e.HasError) 35 36 sharedTransactionIntrinsics(e, &w) 37 38 // totalTime gets put into transaction events but not error events: 39 // https://source.datanerd.us/agents/agent-specs/blob/master/Total-Time-Async.md#attributes 40 w.floatField("totalTime", e.TotalTime.Seconds()) 41 42 // Write better CAT intrinsics if enabled 43 sharedBetterCATIntrinsics(e, &w) 44 45 if e.BetterCAT.Enabled { 46 if p := e.BetterCAT.Inbound; nil != p { 47 if "" != p.TransactionID { 48 w.stringField("parentId", p.TransactionID) 49 } 50 51 if "" != p.ID { 52 w.stringField("parentSpanId", p.ID) 53 } 54 } 55 } 56 57 // Write old CAT intrinsics if enabled 58 oldCATIntrinsics(e, &w) 59 60 buf.WriteByte('}') 61 buf.WriteByte(',') 62 userAttributesJSON(e.Attrs, buf, destTxnEvent, nil) 63 buf.WriteByte(',') 64 agentAttributesJSON(e.Attrs, buf, destTxnEvent) 65 buf.WriteByte(']') 66 } 67 68 // oldCATIntrinsics reports old CAT intrinsics for Transaction 69 // if CrossProcess.Used() is true 70 func oldCATIntrinsics(e *TxnEvent, w *jsonFieldsWriter) { 71 if !e.CrossProcess.Used() { 72 return 73 } 74 75 if e.CrossProcess.ClientID != "" { 76 w.stringField("client_cross_process_id", e.CrossProcess.ClientID) 77 } 78 if e.CrossProcess.TripID != "" { 79 w.stringField("nr.tripId", e.CrossProcess.TripID) 80 } 81 if e.CrossProcess.PathHash != "" { 82 w.stringField("nr.pathHash", e.CrossProcess.PathHash) 83 } 84 if e.CrossProcess.ReferringPathHash != "" { 85 w.stringField("nr.referringPathHash", e.CrossProcess.ReferringPathHash) 86 } 87 if e.CrossProcess.GUID != "" { 88 w.stringField("nr.guid", e.CrossProcess.GUID) 89 } 90 if e.CrossProcess.ReferringTxnGUID != "" { 91 w.stringField("nr.referringTransactionGuid", e.CrossProcess.ReferringTxnGUID) 92 } 93 if len(e.CrossProcess.AlternatePathHashes) > 0 { 94 hashes := make([]string, 0, len(e.CrossProcess.AlternatePathHashes)) 95 for hash := range e.CrossProcess.AlternatePathHashes { 96 hashes = append(hashes, hash) 97 } 98 sort.Strings(hashes) 99 w.stringField("nr.alternatePathHashes", strings.Join(hashes, ",")) 100 } 101 } 102 103 // sharedTransactionIntrinsics reports intrinsics that are shared 104 // by Transaction and TransactionError 105 func sharedTransactionIntrinsics(e *TxnEvent, w *jsonFieldsWriter) { 106 w.floatField("duration", e.Duration.Seconds()) 107 if e.Queuing > 0 { 108 w.floatField("queueDuration", e.Queuing.Seconds()) 109 } 110 if e.externalCallCount > 0 { 111 w.intField("externalCallCount", int64(e.externalCallCount)) 112 w.floatField("externalDuration", e.externalDuration.Seconds()) 113 } 114 if e.datastoreCallCount > 0 { 115 // Note that "database" is used for the keys here instead of 116 // "datastore" for historical reasons. 117 w.intField("databaseCallCount", int64(e.datastoreCallCount)) 118 w.floatField("databaseDuration", e.datastoreDuration.Seconds()) 119 } 120 121 if e.CrossProcess.IsSynthetics() { 122 w.stringField("nr.syntheticsResourceId", e.CrossProcess.Synthetics.ResourceID) 123 w.stringField("nr.syntheticsJobId", e.CrossProcess.Synthetics.JobID) 124 w.stringField("nr.syntheticsMonitorId", e.CrossProcess.Synthetics.MonitorID) 125 } 126 } 127 128 // sharedBetterCATIntrinsics reports intrinsics that are shared 129 // by Transaction, TransactionError, and Slow SQL 130 func sharedBetterCATIntrinsics(e *TxnEvent, w *jsonFieldsWriter) { 131 if e.BetterCAT.Enabled { 132 if p := e.BetterCAT.Inbound; nil != p { 133 w.stringField("parent.type", p.Type) 134 w.stringField("parent.app", p.App) 135 w.stringField("parent.account", p.Account) 136 w.stringField("parent.transportType", p.TransportType) 137 w.floatField("parent.transportDuration", p.TransportDuration.Seconds()) 138 } 139 140 w.stringField("guid", e.BetterCAT.ID) 141 w.stringField("traceId", e.BetterCAT.TraceID()) 142 w.writerField("priority", e.BetterCAT.Priority) 143 w.boolField("sampled", e.BetterCAT.Sampled) 144 } 145 } 146 147 // MarshalJSON is used for testing. 148 func (e *TxnEvent) MarshalJSON() ([]byte, error) { 149 buf := bytes.NewBuffer(make([]byte, 0, 256)) 150 151 e.WriteJSON(buf) 152 153 return buf.Bytes(), nil 154 } 155 156 type txnEvents struct { 157 *analyticsEvents 158 } 159 160 func newTxnEvents(max int) *txnEvents { 161 return &txnEvents{ 162 analyticsEvents: newAnalyticsEvents(max), 163 } 164 } 165 166 func (events *txnEvents) AddTxnEvent(e *TxnEvent, priority Priority) { 167 // Synthetics events always get priority: normal event priorities are in the 168 // range [0.0,1.99999], so adding 2 means that a Synthetics event will always 169 // win. 170 if e.CrossProcess.IsSynthetics() { 171 priority += 2.0 172 } 173 events.addEvent(analyticsEvent{priority: priority, jsonWriter: e}) 174 } 175 176 func (events *txnEvents) MergeIntoHarvest(h *Harvest) { 177 h.TxnEvents.mergeFailed(events.analyticsEvents) 178 } 179 180 func (events *txnEvents) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { 181 return events.CollectorJSON(agentRunID) 182 } 183 184 func (events *txnEvents) EndpointMethod() string { 185 return cmdTxnEvents 186 } 187 188 func (events *txnEvents) payloads(limit int) []PayloadCreator { 189 if events.NumSaved() < float64(limit) { 190 return []PayloadCreator{events} 191 } 192 e1, e2 := events.split() 193 return []PayloadCreator{ 194 &txnEvents{analyticsEvents: e1}, 195 &txnEvents{analyticsEvents: e2}, 196 } 197 }