@@ -665,6 +665,11 @@ class CTraceCollector : public CSummaryCollector
665665public: // IEventVisitor
666666 virtual bool visitEvent (CEvent& event) override
667667 {
668+ // Allow EventQueryStart events to pass through; trace ID to service name
669+ // mapping is built earlier by the metadata parser (CMetaInfoState::visitEvent)
670+ if (event.queryType () == EventQueryStart)
671+ return true ;
672+
668673 // Implicit event filter applied unconditionally
669674 if (queryEventContext (event.queryType ()) != EventCtxIndex)
670675 return true ;
@@ -688,7 +693,7 @@ class CTraceCollector : public CSummaryCollector
688693 virtual void summarize () override
689694 {
690695 // Determine which node kinds have data
691- bool haveNodeKindEntries[ NumNodeKinds] = { false ,} ;
696+ std::fill_n ( haveNodeKindEntries, NumNodeKinds, false ) ;
692697 for (Cache::value_type& entry : stats)
693698 {
694699 TraceStats& traceStats = entry.second ;
@@ -701,23 +706,18 @@ class CTraceCollector : public CSummaryCollector
701706 }
702707 }
703708 }
709+ if (summarization == IndexSummarization::byService)
710+ summarizeByService ();
711+ else
712+ summarizeByTrace ();
713+ }
704714
715+ protected:
716+ void summarizeByTrace ()
717+ {
705718 StringBuffer line;
706719 appendCSVColumns (line, " Trace ID" , " Service Name" , " First Timestamp" , " Last Timestamp" , " Unique Files" );
707-
708- // Add aggregate statistics headers
709- appendNodeKindHeaders (line, " Total " );
710-
711- // Add node kind specific headers only for kinds that have data
712- for (unsigned nodeKind = 0 ; nodeKind < NumNodeKinds; nodeKind++)
713- {
714- if (!haveNodeKindEntries[nodeKind])
715- continue ;
716- const char * nodeKindName = mapNodeKind ((NodeKind)nodeKind);
717- VStringBuffer prefix (" %c%s " , toupper (*nodeKindName), nodeKindName + 1 );
718- appendNodeKindHeaders (line, prefix);
719- }
720-
720+ appendNodeKindHeaders (line);
721721 outputLine (line);
722722
723723 for (Cache::value_type& entry : stats)
@@ -749,7 +749,6 @@ class CTraceCollector : public CSummaryCollector
749749
750750 // Create aggregate statistics across all node kinds
751751 NodeKindStats aggregateStats = {};
752- std::unordered_map<IndexHashKey, uint32_t , IndexHashKeyHash> allNodeMemory;
753752
754753 for (unsigned nodeKind = 0 ; nodeKind < NumNodeKinds; nodeKind++)
755754 aggregateStats.addStats (traceStats.kinds [nodeKind]);
@@ -770,7 +769,115 @@ class CTraceCollector : public CSummaryCollector
770769 }
771770 }
772771
772+ void summarizeByService ()
773+ {
774+ // Extended TraceStats with trace counter for service summarization
775+ class ServiceStats : public TraceStats
776+ {
777+ public:
778+ uint32_t traceCount{0 }; // Count of traces represented in this service
779+ NodeKindStats totals;
780+
781+ ServiceStats () = default ;
782+ void addStats (const TraceStats& other)
783+ {
784+ for (unsigned nodeKind = 0 ; nodeKind < NumNodeKinds; nodeKind++)
785+ {
786+ const NodeKindStats& nodeKindStats = other.kinds [nodeKind];
787+ kinds[nodeKind].addStats (nodeKindStats);
788+ totals.addStats (nodeKindStats);
789+ }
790+ }
791+ };
792+
793+ // Aggregate trace stats by service name
794+ std::unordered_map<std::string, ServiceStats> serviceStats;
795+
796+ for (Cache::value_type& entry : stats)
797+ {
798+ const TraceHashKey& traceKey = entry.first ;
799+ TraceStats& traceStats = entry.second ;
800+
801+ // Find service name for this trace ID, or use empty string if not found
802+ const char * serviceName = operation.queryMetaInfoState ().queryServiceName (traceKey.traceId .str ());
803+ if (!serviceName)
804+ serviceName = " " ;
805+ ServiceStats& serviceAggregateStats = serviceStats[serviceName];
806+
807+ // Aggregate the stats
808+ serviceAggregateStats.addStats (traceStats);
809+
810+ // Increment trace count
811+ serviceAggregateStats.traceCount ++;
812+
813+ // Merge unique files
814+ serviceAggregateStats.uniqueFileIds .insert (traceStats.uniqueFileIds .begin (), traceStats.uniqueFileIds .end ());
815+
816+ // Update timestamp ranges
817+ if (traceStats.firstTimestamp != UINT64_MAX)
818+ serviceAggregateStats.firstTimestamp = std::min (serviceAggregateStats.firstTimestamp , traceStats.firstTimestamp );
819+ if (traceStats.lastTimestamp > 0 )
820+ serviceAggregateStats.lastTimestamp = std::max (serviceAggregateStats.lastTimestamp , traceStats.lastTimestamp );
821+ }
822+
823+ // Output aggregated service stats
824+ StringBuffer line;
825+ appendCSVColumns (line, " Service Name" , " Trace Count" , " First Timestamp" , " Last Timestamp" , " Unique Files" );
826+ appendNodeKindHeaders (line);
827+ outputLine (line);
828+
829+ for (auto & serviceEntry : serviceStats)
830+ {
831+ const std::string& serviceName = serviceEntry.first ;
832+ ServiceStats& serviceStatsEntry = serviceEntry.second ;
833+
834+ // Format timestamps using CDateTime (empty if no valid timestamps were found)
835+ StringBuffer firstTsStr, lastTsStr;
836+ if (serviceStatsEntry.firstTimestamp != UINT64_MAX)
837+ {
838+ CDateTime firstDt;
839+ firstDt.setTimeStampNs (serviceStatsEntry.firstTimestamp );
840+ firstDt.getString (firstTsStr);
841+ }
842+ if (serviceStatsEntry.lastTimestamp > 0 )
843+ {
844+ CDateTime lastDt;
845+ lastDt.setTimeStampNs (serviceStatsEntry.lastTimestamp );
846+ lastDt.getString (lastTsStr);
847+ }
848+
849+ const char * displayServiceName = serviceName.empty () ? " <no service>" : serviceName.c_str ();
850+ appendCSVColumns (line, displayServiceName, serviceStatsEntry.traceCount , firstTsStr.str (), lastTsStr.str (), serviceStatsEntry.uniqueFileIds .size ());
851+ appendNodeKindData (line, serviceStatsEntry.totals );
852+ for (unsigned nodeKind = 0 ; nodeKind < NumNodeKinds; nodeKind++)
853+ {
854+ if (!haveNodeKindEntries[nodeKind])
855+ continue ;
856+ const NodeKindStats& nodeStats = serviceStatsEntry.kinds [nodeKind];
857+ appendNodeKindData (line, nodeStats);
858+ }
859+ outputLine (line);
860+ }
861+ }
862+
773863protected:
864+
865+ void appendNodeKindHeaders (StringBuffer& line)
866+ {
867+ // Add aggregate statistics headers
868+ appendNodeKindHeaders (line, " Total " );
869+
870+ // Add node kind specific headers only for kinds that have data
871+ for (unsigned nodeKind = 0 ; nodeKind < NumNodeKinds; nodeKind++)
872+ {
873+ if (!haveNodeKindEntries[nodeKind])
874+ continue ;
875+ const char * nodeKindName = mapNodeKind ((NodeKind)nodeKind);
876+ VStringBuffer prefix (" %c%s " , toupper (*nodeKindName), nodeKindName + 1 );
877+ appendNodeKindHeaders (line, prefix);
878+ }
879+ }
880+
774881 void appendNodeKindHeaders (StringBuffer& line, const char * prefix)
775882 {
776883 appendCSVEventsHeaders (line, prefix);
@@ -807,6 +914,8 @@ class CTraceCollector : public CSummaryCollector
807914protected:
808915 using Cache = std::unordered_map<TraceHashKey, TraceStats, TraceHashKeyHash>;
809916 Cache stats;
917+ bool haveNodeKindEntries[NumNodeKinds] = {false ,};
918+
810919};
811920
812921bool CIndexFileSummary::doOp ()
@@ -820,6 +929,7 @@ bool CIndexFileSummary::doOp()
820929 collector.setown (new CNodeCollector (*this , summarization, out));
821930 break ;
822931 case IndexSummarization::byTrace:
932+ case IndexSummarization::byService:
823933 collector.setown (new CTraceCollector (*this , summarization, out));
824934 break ;
825935 default :
0 commit comments