@cryptotaxi247 / netdata / commits / c30192e39

as400 improvements (#21158)

* added percentage of cpu entitled * added as400 query latency chart

Costa Tsaousis committed Oct 16, 2025 at 13:08 UTC c30192e39a32d4bf0fdf077048c4e7d552dc333a
9 files changed +715 -36
src/go/plugin/ibm.d/modules/as400/README.md
+1
@@ -122,6 +122,7 @@ Metrics:
122 | Metric | Dimensions | Unit |
123 |:-------|:-----------|:-----|
124 | as400.cpu_utilization | utilization | percentage |
125 +| as400.cpu_utilization_entitled | utilization | percentage |
126 | as400.cpu_configuration | configured | cpus |
127 | as400.cpu_capacity | capacity | percentage |
128 | as400.total_jobs | total | jobs |
src/go/plugin/ibm.d/modules/as400/collect_activejobs.go
+2 -2
@@ -14,7 +14,7 @@ import (
14 // countActiveJobs returns the number of active jobs for cardinality check
15 func (a *Collector) countActiveJobs(ctx context.Context) (int, error) {
16 var count int
17 - err := a.doQueryRow(ctx, queryCountActiveJobs, func(column, value string) {
17 + err := a.doQueryRow(ctx, "count_active_jobs", queryCountActiveJobs, func(column, value string) {
18 if column == "COUNT" {
19 if v, err := strconv.Atoi(value); err == nil {
20 count = v
@@ -48,7 +48,7 @@ func (a *Collector) collectActiveJobs(ctx context.Context) error {
48 currentJobName string
49 currentJob *activeJobMetrics
50 )
51 - err = a.doQuery(ctx, query, func(column, value string, lineEnd bool) {
51 + err = a.doQuery(ctx, "top_active_jobs", query, func(column, value string, lineEnd bool) {
52 switch column {
53 case "JOB_NAME":
54 currentJobName = value
src/go/plugin/ibm.d/modules/as400/collect_data.go
+87 -27
@@ -82,6 +82,22 @@ func (a *Collector) parseInt64Value(value string, multiplier int64) (int64, bool
82
83 // parseFloat64Value parses a value as float64, returns (result, ok)
84 // Logs all parse attempts in debug mode
85 +func (a *Collector) computeEntitledCPUPercentage(cpuUtilization float64) int64 {
86 + if a.mx.CurrentCPUCapacity <= 0 {
87 + return 0
88 + }
89 + capacityPercent := float64(a.mx.CurrentCPUCapacity) / float64(precision)
90 + if capacityPercent <= 0 {
91 + return 0
92 + }
93 + perCorePercent := cpuUtilization / float64(precision)
94 + entitled := (perCorePercent / capacityPercent) * 100.0
95 + if entitled < 0 {
96 + entitled = 0
97 + }
98 + return int64(math.Round(entitled * float64(precision)))
99 +}
100 +
101 func (a *Collector) parseFloat64Value(value string) (float64, bool) {
102 cleaned := cleanNumericString(value)
103 if cleaned == "" || cleaned == "-" || cleaned == "." || cleaned == "+" {
@@ -203,9 +219,34 @@ func (a *Collector) collect(ctx context.Context) error {
219 return nil
220 }
221
222 +func (a *Collector) recordQueryLatency(queryName string, duration time.Duration) {
223 + if a.mx == nil {
224 + return
225 + }
226 + if queryName == "" {
227 + queryName = "unknown_query"
228 + }
229 +
230 + sanitized := cleanName(queryName)
231 + if sanitized == "" {
232 + sanitized = "unknown_query"
233 + }
234 +
235 + if a.mx.queryLatencies == nil {
236 + a.mx.queryLatencies = make(map[string]int64)
237 + }
238 +
239 + latency := duration.Microseconds()
240 + if latency == 0 && duration > 0 {
241 + latency = 1
242 + }
243 +
244 + a.mx.queryLatencies[sanitized] += latency
245 +}
246 +
247 func (a *Collector) collectSystemStatus(ctx context.Context) error {
248 // Use comprehensive query to get all system status metrics at once
208 - err := a.doQuery(ctx, a.systemStatusQuery(), func(column, value string, lineEnd bool) {
249 + err := a.doQuery(ctx, "system_status", a.systemStatusQuery(), func(column, value string, lineEnd bool) {
250 // Debug log all columns to see what we're receiving
251 if strings.Contains(column, "STORAGE") || strings.Contains(column, "MEMORY") {
252 a.Debugf("collectSystemStatus: column='%s', value='%s'", column, value)
@@ -222,6 +263,7 @@ func (a *Collector) collectSystemStatus(ctx context.Context) error {
263 // AVERAGE_CPU_UTILIZATION is system-wide 0-100% (deprecated in IBM i 7.4+)
264 if v, ok := a.parseInt64Value(value, precision); ok {
265 a.mx.CPUPercentage = v
266 + a.mx.EntitledCPUPercentage = a.computeEntitledCPUPercentage(float64(v))
267 }
268 case "CURRENT_CPU_CAPACITY":
269 // CURRENT_CPU_CAPACITY comes from IBM as decimal fraction (0.0-1.0)
@@ -302,7 +344,7 @@ func (a *Collector) collectSystemStatus(ctx context.Context) error {
344
345 func (a *Collector) collectMemoryPools(ctx context.Context) error {
346 var currentPoolName string
305 - return a.doQuery(ctx, a.memoryPoolQuery(), func(column, value string, lineEnd bool) {
347 + return a.doQuery(ctx, "memory_pools", a.memoryPoolQuery(), func(column, value string, lineEnd bool) {
348 switch column {
349 case "POOL_NAME":
350 currentPoolName = strings.TrimSpace(value)
@@ -361,7 +403,7 @@ func (a *Collector) collectMemoryPools(ctx context.Context) error {
403
404 func (a *Collector) collectDiskStatus(ctx context.Context) error {
405 // Try modern query first
364 - err := a.doQuery(ctx, queryDiskStatus, func(column, value string, lineEnd bool) {
406 + err := a.doQuery(ctx, "disk_status", queryDiskStatus, func(column, value string, lineEnd bool) {
407 if column == "AVG_DISK_BUSY" {
408 if v, ok := a.parseInt64Value(value, precision); ok {
409 a.mx.DiskBusyPercentage = v
@@ -374,7 +416,7 @@ func (a *Collector) collectDiskStatus(ctx context.Context) error {
416
417 func (a *Collector) collectJobInfo(ctx context.Context) error {
418 // Try modern query first
377 - err := a.doQuery(ctx, queryJobInfo, func(column, value string, lineEnd bool) {
419 + err := a.doQuery(ctx, "job_info", queryJobInfo, func(column, value string, lineEnd bool) {
420 if column == "JOB_QUEUE_LENGTH" {
421 if v, ok := a.parseInt64Value(value, 1); ok {
422 a.mx.JobQueueLength = v
@@ -415,7 +457,7 @@ func (a *Collector) collectMessageQueues(ctx context.Context) error {
457 metrics messageQueueInstanceMetrics
458 )
459
418 - return a.doQuery(ctx, query, func(column, value string, lineEnd bool) {
460 + return a.doQuery(ctx, "message_queue_aggregates", query, func(column, value string, lineEnd bool) {
461 switch column {
462 case "MESSAGE_QUEUE_LIBRARY":
463 library = normalizeValue(value)
@@ -486,7 +528,7 @@ func (a *Collector) collectOutputQueues(ctx context.Context) error {
528 metrics outputQueueInstanceMetrics
529 )
530
489 - return a.doQuery(ctx, query, func(column, value string, lineEnd bool) {
531 + return a.doQuery(ctx, "output_queue_info", query, func(column, value string, lineEnd bool) {
532 switch column {
533 case "OUTPUT_QUEUE_LIBRARY_NAME":
534 library = normalizeValue(value)
@@ -519,7 +561,7 @@ func (a *Collector) collectOutputQueues(ctx context.Context) error {
561 })
562 }
563
522 -func (a *Collector) doQuery(ctx context.Context, query string, assign func(column, value string, lineEnd bool)) error {
564 +func (a *Collector) doQuery(ctx context.Context, queryName, query string, assign func(column, value string, lineEnd bool)) error {
565 var (
566 capture bool
567 columnsSaved []string
@@ -528,6 +570,12 @@ func (a *Collector) doQuery(ctx context.Context, query string, assign func(colum
570 if a.dump != nil {
571 capture = true
572 }
573 +
574 + start := time.Now()
575 + defer func() {
576 + a.recordQueryLatency(queryName, time.Since(start))
577 + }()
578 +
579 err := a.client.Query(ctx, query, func(columns []string, values []string) error {
580 for i, col := range columns {
581 assign(col, values[i], i == len(columns)-1)
@@ -559,7 +607,7 @@ func (a *Collector) doQuery(ctx context.Context, query string, assign func(colum
607 }
608
609 // doQueryRow executes a query that returns a single row
562 -func (a *Collector) doQueryRow(ctx context.Context, query string, assign func(column, value string)) error {
610 +func (a *Collector) doQueryRow(ctx context.Context, queryName, query string, assign func(column, value string)) error {
611 var (
612 capture bool
613 columnsSaved []string
@@ -568,6 +616,12 @@ func (a *Collector) doQueryRow(ctx context.Context, query string, assign func(co
616 if a.dump != nil {
617 capture = true
618 }
619 +
620 + start := time.Now()
621 + defer func() {
622 + a.recordQueryLatency(queryName, time.Since(start))
623 + }()
624 +
625 err := a.client.QueryWithLimit(ctx, query, 1, func(columns []string, values []string) error {
626 for i, col := range columns {
627 assign(col, values[i])
@@ -609,7 +663,7 @@ func (a *Collector) collectDiskInstances(ctx context.Context) error {
663 }
664
665 var currentUnit string
612 - return a.doQuery(ctx, queryDiskInstances, func(column, value string, lineEnd bool) {
666 + return a.doQuery(ctx, "disk_instances", queryDiskInstances, func(column, value string, lineEnd bool) {
667
668 switch column {
669 case "UNIT_NUMBER":
@@ -843,7 +897,7 @@ func (a *Collector) collectDiskInstances(ctx context.Context) error {
897
898 func (a *Collector) countDisks(ctx context.Context) (int, error) {
899 var count int
846 - err := a.doQuery(ctx, queryCountDisks, func(column, value string, lineEnd bool) {
900 + err := a.doQuery(ctx, "count_disks", queryCountDisks, func(column, value string, lineEnd bool) {
901 if column == "COUNT" {
902 count = int(parseInt64OrZero(value))
903 }
@@ -853,7 +907,7 @@ func (a *Collector) countDisks(ctx context.Context) (int, error) {
907
908 // Network connections collection
909 func (a *Collector) collectNetworkConnections(ctx context.Context) error {
856 - return a.doQuery(ctx, queryNetworkConnections, func(column, value string, lineEnd bool) {
910 + return a.doQuery(ctx, "network_connections", queryNetworkConnections, func(column, value string, lineEnd bool) {
911 switch column {
912 case "REMOTE_CONNECTIONS":
913 if v, ok := a.parseInt64Value(value, 1); ok {
@@ -877,7 +931,7 @@ func (a *Collector) collectNetworkConnections(ctx context.Context) error {
931
932 func (a *Collector) countNetworkInterfaces(ctx context.Context) (int, error) {
933 var count int
880 - err := a.doQueryRow(ctx, queryCountNetworkInterfaces, func(column, value string) {
934 + err := a.doQueryRow(ctx, "count_network_interfaces", queryCountNetworkInterfaces, func(column, value string) {
935 if column == "COUNT" {
936 if v, ok := a.parseInt64Value(value, 1); ok {
937 count = int(v)
@@ -889,7 +943,7 @@ func (a *Collector) countNetworkInterfaces(ctx context.Context) (int, error) {
943
944 func (a *Collector) countMessageQueues(ctx context.Context) (int, error) {
945 var count int
892 - err := a.doQueryRow(ctx, queryCountMessageQueues, func(column, value string) {
946 + err := a.doQueryRow(ctx, "count_message_queues", queryCountMessageQueues, func(column, value string) {
947 if column == "COUNT" {
948 if v, ok := a.parseInt64Value(value, 1); ok {
949 count = int(v)
@@ -901,7 +955,7 @@ func (a *Collector) countMessageQueues(ctx context.Context) (int, error) {
955
956 func (a *Collector) countOutputQueues(ctx context.Context) (int, error) {
957 var count int
904 - err := a.doQueryRow(ctx, queryCountOutputQueues, func(column, value string) {
958 + err := a.doQueryRow(ctx, "count_output_queues", queryCountOutputQueues, func(column, value string) {
959 if column == "COUNT" {
960 if v, ok := a.parseInt64Value(value, 1); ok {
961 count = int(v)
@@ -913,7 +967,7 @@ func (a *Collector) countOutputQueues(ctx context.Context) (int, error) {
967
968 func (a *Collector) countHTTPServers(ctx context.Context) (int, error) {
969 var count int64
916 - err := a.doQueryRow(ctx, queryCountHTTPServers, func(column, value string) {
970 + err := a.doQueryRow(ctx, "count_http_servers", queryCountHTTPServers, func(column, value string) {
971 if column == "COUNT" {
972 if v, ok := a.parseInt64Value(value, 1); ok {
973 count = v
@@ -937,7 +991,7 @@ func withFetchLimit(query string, limit int) string {
991
992 func (a *Collector) countSubsystems(ctx context.Context) (int, error) {
993 var count int64
940 - err := a.doQueryRow(ctx, queryCountSubsystems, func(column, value string) {
994 + err := a.doQueryRow(ctx, "count_subsystems", queryCountSubsystems, func(column, value string) {
995 if column == "COUNT" {
996 if v, ok := a.parseInt64Value(value, 1); ok {
997 count = v
@@ -949,7 +1003,7 @@ func (a *Collector) countSubsystems(ctx context.Context) (int, error) {
1003
1004 func (a *Collector) countJobQueues(ctx context.Context) (int, error) {
1005 var count int64
952 - err := a.doQueryRow(ctx, queryCountJobQueues, func(column, value string) {
1006 + err := a.doQueryRow(ctx, "count_job_queues", queryCountJobQueues, func(column, value string) {
1007 if column == "COUNT" {
1008 if v, ok := a.parseInt64Value(value, 1); ok {
1009 count = v
@@ -962,7 +1016,7 @@ func (a *Collector) countJobQueues(ctx context.Context) (int, error) {
1016 // Temporary storage collection
1017 func (a *Collector) collectTempStorage(ctx context.Context) error {
1018 // Collect total temp storage
965 - err := a.doQuery(ctx, queryTempStorageTotal, func(column, value string, lineEnd bool) {
1019 + err := a.doQuery(ctx, "temp_storage_total", queryTempStorageTotal, func(column, value string, lineEnd bool) {
1020 switch column {
1021 case "CURRENT_SIZE":
1022 if v, ok := a.parseInt64Value(value, 1); ok {
@@ -980,7 +1034,7 @@ func (a *Collector) collectTempStorage(ctx context.Context) error {
1034
1035 // Collect named temp storage buckets
1036 var currentBucket string
983 - return a.doQuery(ctx, queryTempStorageNamed, func(column, value string, lineEnd bool) {
1037 + return a.doQuery(ctx, "temp_storage_named", queryTempStorageNamed, func(column, value string, lineEnd bool) {
1038 switch column {
1039 case "NAME":
1040 currentBucket = value
@@ -1025,7 +1079,7 @@ func (a *Collector) collectSubsystems(ctx context.Context) error {
1079 }
1080
1081 var currentSubsystem string
1028 - return a.doQuery(ctx, query, func(column, value string, lineEnd bool) {
1082 + return a.doQuery(ctx, "subsystems", query, func(column, value string, lineEnd bool) {
1083 switch column {
1084 case "SUBSYSTEM_NAME":
1085 name := strings.TrimSpace(value)
@@ -1093,7 +1147,7 @@ func (a *Collector) collectJobQueues(ctx context.Context) error {
1147 }
1148
1149 var currentQueue string
1096 - return a.doQuery(ctx, query, func(column, value string, lineEnd bool) {
1150 + return a.doQuery(ctx, "job_queues", query, func(column, value string, lineEnd bool) {
1151 switch column {
1152 case "QUEUE_NAME":
1153 name := strings.TrimSpace(value)
@@ -1153,7 +1207,7 @@ func (a *Collector) collectDiskInstancesEnhanced(ctx context.Context) error {
1207 }
1208
1209 var currentUnit string
1156 - return a.doQuery(ctx, queryDiskInstancesEnhanced, func(column, value string, lineEnd bool) {
1210 + return a.doQuery(ctx, "disk_instances_enhanced", queryDiskInstancesEnhanced, func(column, value string, lineEnd bool) {
1211 switch column {
1212 case "UNIT_NUMBER":
1213 currentUnit = value
@@ -1333,7 +1387,7 @@ func (a *Collector) collectNetworkInterfaces(ctx context.Context) error {
1387 }
1388
1389 var currentInterface string
1336 - return a.doQuery(ctx, queryNetworkInterfaces, func(column, value string, lineEnd bool) {
1390 + return a.doQuery(ctx, "network_interfaces", queryNetworkInterfaces, func(column, value string, lineEnd bool) {
1391 switch column {
1392 case "LINE_DESCRIPTION":
1393 iface := strings.TrimSpace(value)
@@ -1444,7 +1498,7 @@ func (a *Collector) collectHTTPServerInfo(ctx context.Context) error {
1498 currentKey string
1499 )
1500
1447 - return a.doQuery(ctx, queryHTTPServerInfo, func(column, value string, lineEnd bool) {
1501 + return a.doQuery(ctx, "http_server_info", queryHTTPServerInfo, func(column, value string, lineEnd bool) {
1502 switch column {
1503 case "SERVER_NAME":
1504 serverName = strings.TrimSpace(value)
@@ -1561,10 +1615,12 @@ func (a *Collector) collectPlanCache(ctx context.Context) error {
1615 if err := a.client.Exec(ctx, callAnalyzePlanCache); err != nil {
1616 return fmt.Errorf("failed to analyze plan cache: %w", err)
1617 }
1564 - a.Debugf("plan cache analysis completed in %v", time.Since(start))
1618 + elapsed := time.Since(start)
1619 + a.Debugf("plan cache analysis completed in %v", elapsed)
1620 + a.recordQueryLatency("analyze_plan_cache", elapsed)
1621
1622 var currentHeading string
1567 - return a.doQuery(ctx, queryPlanCacheSummary, func(column, value string, lineEnd bool) {
1623 + return a.doQuery(ctx, "plan_cache_summary", queryPlanCacheSummary, func(column, value string, lineEnd bool) {
1624 switch column {
1625 case "HEADING":
1626 currentHeading = strings.TrimSpace(value)
@@ -1599,6 +1655,8 @@ func (a *Collector) collectSystemActivity(ctx context.Context) error {
1655 // Query both potential data sources in one query
1656 query := a.systemActivityQuery()
1657
1658 + a.mx.EntitledCPUPercentage = 0
1659 +
1660 var (
1661 totalCPUTime int64 // Nanoseconds since IPL (NULL if no *JOBCTL)
1662 elapsedTime int64 // Seconds since last reset
@@ -1607,7 +1665,7 @@ func (a *Collector) collectSystemActivity(ctx context.Context) error {
1665 hasElapsedData bool
1666 )
1667
1610 - err := a.doQuery(ctx, query, func(column, value string, lineEnd bool) {
1668 + err := a.doQuery(ctx, "system_activity", query, func(column, value string, lineEnd bool) {
1669 switch column {
1670 case "TOTAL_CPU_TIME":
1671 // This will be NULL if user doesn't have *JOBCTL authority
@@ -1665,6 +1723,7 @@ func (a *Collector) collectSystemActivity(ctx context.Context) error {
1723 a.mx.systemActivity.AverageCPUUtilization = int64(cpuUtilization)
1724 a.mx.systemActivity.AverageCPURate = int64(cpuUtilization)
1725 a.mx.CPUPercentage = int64(cpuUtilization)
1726 + a.mx.EntitledCPUPercentage = a.computeEntitledCPUPercentage(cpuUtilization)
1727 } else {
1728 if cpuUtilization < 0 {
1729 a.Warningf("CPU collection: calculated utilization negative (%.2f%%), skipping this sample", cpuUtilization/precision)
@@ -1718,6 +1777,7 @@ func (a *Collector) collectSystemActivity(ctx context.Context) error {
1777 a.mx.systemActivity.AverageCPUUtilization = int64(cpuUtilization)
1778 a.mx.systemActivity.AverageCPURate = int64(cpuUtilization)
1779 a.mx.CPUPercentage = int64(cpuUtilization)
1780 + a.mx.EntitledCPUPercentage = a.computeEntitledCPUPercentage(cpuUtilization)
1781 } else {
1782 if cpuUtilization < 0 {
1783 a.Warningf("CPU collection: interval utilization negative (%.2f%%), skipping this sample", cpuUtilization/precision)
src/go/plugin/ibm.d/modules/as400/collector.go
+78
@@ -112,6 +112,7 @@ func (c *Collector) resetInstanceCaches() {
112 c.mx.networkInterfaces = make(map[string]networkInterfaceInstanceMetrics)
113 c.mx.httpServers = make(map[string]httpServerInstanceMetrics)
114 c.mx.planCache = make(map[string]planCacheInstanceMetrics)
115 + c.mx.queryLatencies = make(map[string]int64)
116 }
117
118 func (c *Collector) prepareIterationState() {
@@ -184,6 +185,7 @@ func (c *Collector) CollectOnce() error {
185 c.exportSystemActivityMetrics()
186 c.exportHTTPServerMetrics()
187 c.exportPlanCacheMetrics()
188 + c.exportQueryLatencyMetrics()
189 c.applyGlobalLabels()
190
191 return nil
@@ -334,6 +336,10 @@ func (c *Collector) exportSystemMetrics() {
336 Utilization: c.mx.CPUPercentage,
337 })
338
339 + contexts.System.CPUEntitledUtilization.Set(c.State, labels, contexts.SystemCPUEntitledUtilizationValues{
340 + Utilization: c.mx.EntitledCPUPercentage,
341 + })
342 +
343 contexts.System.CPUDetails.Set(c.State, labels, contexts.SystemCPUDetailsValues{
344 Configured: c.mx.ConfiguredCPUs,
345 })
@@ -772,6 +778,78 @@ func (c *Collector) exportPlanCacheMetrics() {
778 }
779 }
780
781 +func (c *Collector) exportQueryLatencyMetrics() {
782 + if c.mx == nil || len(c.mx.queryLatencies) == 0 {
783 + return
784 + }
785 +
786 + values := contexts.ObservabilityQueryLatencyValues{}
787 + fieldMap := map[string]*int64{
788 + "analyze_plan_cache": &values.Analyze_plan_cache,
789 + "count_active_jobs": &values.Count_active_jobs,
790 + "count_disks": &values.Count_disks,
791 + "count_http_servers": &values.Count_http_servers,
792 + "count_job_queues": &values.Count_job_queues,
793 + "count_message_queues": &values.Count_message_queues,
794 + "count_network_interfaces": &values.Count_network_interfaces,
795 + "count_output_queues": &values.Count_output_queues,
796 + "count_subsystems": &values.Count_subsystems,
797 + "detect_ibmi_version_primary": &values.Detect_ibmi_version_primary,
798 + "detect_ibmi_version_fallback": &values.Detect_ibmi_version_fallback,
799 + "disk_instances": &values.Disk_instances,
800 + "disk_instances_enhanced": &values.Disk_instances_enhanced,
801 + "disk_status": &values.Disk_status,
802 + "http_server_info": &values.Http_server_info,
803 + "job_info": &values.Job_info,
804 + "job_queues": &values.Job_queues,
805 + "memory_pools": &values.Memory_pools,
806 + "message_queue_aggregates": &values.Message_queue_aggregates,
807 + "network_connections": &values.Network_connections,
808 + "network_interfaces": &values.Network_interfaces,
809 + "output_queue_info": &values.Output_queue_info,
810 + "plan_cache_summary": &values.Plan_cache_summary,
811 + "serial_number": &values.Serial_number,
812 + "system_activity": &values.System_activity,
813 + "system_model": &values.System_model,
814 + "system_status": &values.System_status,
815 + "temp_storage_named": &values.Temp_storage_named,
816 + "temp_storage_total": &values.Temp_storage_total,
817 + "technology_refresh_level": &values.Technology_refresh_level,
818 + "top_active_jobs": &values.Top_active_jobs,
819 + }
820 +
821 + var otherTotal int64
822 +
823 + for name, latency := range c.mx.queryLatencies {
824 + if latency == 0 {
825 + continue
826 + }
827 + if target, ok := fieldMap[name]; ok {
828 + *target += latency
829 + } else {
830 + otherTotal += latency
831 + }
832 + }
833 +
834 + if otherTotal > 0 {
835 + values.Other = otherTotal
836 + }
837 +
838 + var total int64
839 + for _, ptr := range fieldMap {
840 + if ptr != nil {
841 + total += *ptr
842 + }
843 + }
844 + total += values.Other
845 +
846 + if total == 0 {
847 + return
848 + }
849 +
850 + contexts.Observability.QueryLatency.Set(c.State, contexts.EmptyLabels{}, values)
851 +}
852 +
853 func (c *Collector) exportSystemActivityMetrics() {
854 if c.mx.systemActivity.AverageCPURate == 0 && c.mx.systemActivity.AverageCPUUtilization == 0 {
855 return
src/go/plugin/ibm.d/modules/as400/contexts/contexts.yaml
+118
@@ -12,6 +12,17 @@ System:
12 - name: utilization
13 algo: absolute
14 div: 1000
15 + - name: CPUEntitledUtilization
16 + context: as400.cpu_utilization_entitled
17 + title: CPU Utilization (as % of entitlement)
18 + family: compute/cpu
19 + units: percentage
20 + type: line
21 + priority: 102
22 + dimensions:
23 + - name: utilization
24 + algo: absolute
25 + div: 1000
26 - name: CPUDetails
27 context: as400.cpu_configuration
28 title: CPU Configuration
@@ -643,3 +654,110 @@ PlanCache:
654 - name: value
655 algo: absolute
656 div: 1000
657 +Observability:
658 + labels: []
659 + contexts:
660 + - name: QueryLatency
661 + context: netdata.plugin_ibm.as400_query_latency
662 + title: AS400 Query Latency
663 + family: plugins/ibm.d/latency
664 + units: ms
665 + type: stacked
666 + priority: 146000
667 + dimensions:
668 + - name: analyze_plan_cache
669 + algo: absolute
670 + div: 1000
671 + - name: count_active_jobs
672 + algo: absolute
673 + div: 1000
674 + - name: count_disks
675 + algo: absolute
676 + div: 1000
677 + - name: count_http_servers
678 + algo: absolute
679 + div: 1000
680 + - name: count_job_queues
681 + algo: absolute
682 + div: 1000
683 + - name: count_message_queues
684 + algo: absolute
685 + div: 1000
686 + - name: count_network_interfaces
687 + algo: absolute
688 + div: 1000
689 + - name: count_output_queues
690 + algo: absolute
691 + div: 1000
692 + - name: count_subsystems
693 + algo: absolute
694 + div: 1000
695 + - name: detect_ibmi_version_primary
696 + algo: absolute
697 + div: 1000
698 + - name: detect_ibmi_version_fallback
699 + algo: absolute
700 + div: 1000
701 + - name: disk_instances
702 + algo: absolute
703 + div: 1000
704 + - name: disk_instances_enhanced
705 + algo: absolute
706 + div: 1000
707 + - name: disk_status
708 + algo: absolute
709 + div: 1000
710 + - name: http_server_info
711 + algo: absolute
712 + div: 1000
713 + - name: job_info
714 + algo: absolute
715 + div: 1000
716 + - name: job_queues
717 + algo: absolute
718 + div: 1000
719 + - name: memory_pools
720 + algo: absolute
721 + div: 1000
722 + - name: message_queue_aggregates
723 + algo: absolute
724 + div: 1000
725 + - name: network_connections
726 + algo: absolute
727 + div: 1000
728 + - name: network_interfaces
729 + algo: absolute
730 + div: 1000
731 + - name: output_queue_info
732 + algo: absolute
733 + div: 1000
734 + - name: plan_cache_summary
735 + algo: absolute
736 + div: 1000
737 + - name: serial_number
738 + algo: absolute
739 + div: 1000
740 + - name: system_activity
741 + algo: absolute
742 + div: 1000
743 + - name: system_model
744 + algo: absolute
745 + div: 1000
746 + - name: system_status
747 + algo: absolute
748 + div: 1000
749 + - name: temp_storage_named
750 + algo: absolute
751 + div: 1000
752 + - name: temp_storage_total
753 + algo: absolute
754 + div: 1000
755 + - name: technology_refresh_level
756 + algo: absolute
757 + div: 1000
758 + - name: top_active_jobs
759 + algo: absolute
760 + div: 1000
761 + - name: other
762 + algo: absolute
763 + div: 1000
src/go/plugin/ibm.d/modules/as400/contexts/zz_generated_contexts.go
+376
@@ -1329,6 +1329,336 @@ var NetworkInterface = struct {
1329 },
1330 }
1331
1332 +// --- Observability ---
1333 +
1334 +// ObservabilityQueryLatencyValues defines the type-safe values for Observability.QueryLatency context
1335 +type ObservabilityQueryLatencyValues struct {
1336 + Analyze_plan_cache int64
1337 + Count_active_jobs int64
1338 + Count_disks int64
1339 + Count_http_servers int64
1340 + Count_job_queues int64
1341 + Count_message_queues int64
1342 + Count_network_interfaces int64
1343 + Count_output_queues int64
1344 + Count_subsystems int64
1345 + Detect_ibmi_version_primary int64
1346 + Detect_ibmi_version_fallback int64
1347 + Disk_instances int64
1348 + Disk_instances_enhanced int64
1349 + Disk_status int64
1350 + Http_server_info int64
1351 + Job_info int64
1352 + Job_queues int64
1353 + Memory_pools int64
1354 + Message_queue_aggregates int64
1355 + Network_connections int64
1356 + Network_interfaces int64
1357 + Output_queue_info int64
1358 + Plan_cache_summary int64
1359 + Serial_number int64
1360 + System_activity int64
1361 + System_model int64
1362 + System_status int64
1363 + Temp_storage_named int64
1364 + Temp_storage_total int64
1365 + Technology_refresh_level int64
1366 + Top_active_jobs int64
1367 + Other int64
1368 +}
1369 +
1370 +// ObservabilityQueryLatencyContext provides type-safe operations for Observability.QueryLatency context
1371 +type ObservabilityQueryLatencyContext struct {
1372 + framework.Context[EmptyLabels]
1373 +}
1374 +
1375 +// Set provides type-safe dimension setting for Observability.QueryLatency context
1376 +func (c ObservabilityQueryLatencyContext) Set(state *framework.CollectorState, labels EmptyLabels, values ObservabilityQueryLatencyValues) {
1377 + state.SetMetricsForGeneratedCode(&c.Context, nil, map[string]int64{
1378 + "analyze_plan_cache": values.Analyze_plan_cache,
1379 + "count_active_jobs": values.Count_active_jobs,
1380 + "count_disks": values.Count_disks,
1381 + "count_http_servers": values.Count_http_servers,
1382 + "count_job_queues": values.Count_job_queues,
1383 + "count_message_queues": values.Count_message_queues,
1384 + "count_network_interfaces": values.Count_network_interfaces,
1385 + "count_output_queues": values.Count_output_queues,
1386 + "count_subsystems": values.Count_subsystems,
1387 + "detect_ibmi_version_primary": values.Detect_ibmi_version_primary,
1388 + "detect_ibmi_version_fallback": values.Detect_ibmi_version_fallback,
1389 + "disk_instances": values.Disk_instances,
1390 + "disk_instances_enhanced": values.Disk_instances_enhanced,
1391 + "disk_status": values.Disk_status,
1392 + "http_server_info": values.Http_server_info,
1393 + "job_info": values.Job_info,
1394 + "job_queues": values.Job_queues,
1395 + "memory_pools": values.Memory_pools,
1396 + "message_queue_aggregates": values.Message_queue_aggregates,
1397 + "network_connections": values.Network_connections,
1398 + "network_interfaces": values.Network_interfaces,
1399 + "output_queue_info": values.Output_queue_info,
1400 + "plan_cache_summary": values.Plan_cache_summary,
1401 + "serial_number": values.Serial_number,
1402 + "system_activity": values.System_activity,
1403 + "system_model": values.System_model,
1404 + "system_status": values.System_status,
1405 + "temp_storage_named": values.Temp_storage_named,
1406 + "temp_storage_total": values.Temp_storage_total,
1407 + "technology_refresh_level": values.Technology_refresh_level,
1408 + "top_active_jobs": values.Top_active_jobs,
1409 + "other": values.Other,
1410 + })
1411 +}
1412 +
1413 +// SetUpdateEvery sets the update interval for this instance
1414 +func (c ObservabilityQueryLatencyContext) SetUpdateEvery(state *framework.CollectorState, labels EmptyLabels, updateEvery int) {
1415 + state.SetUpdateEveryOverrideForGeneratedCode(&c.Context, nil, updateEvery)
1416 +}
1417 +
1418 +// Observability contains all metric contexts for Observability
1419 +var Observability = struct {
1420 + QueryLatency ObservabilityQueryLatencyContext
1421 +}{
1422 + QueryLatency: ObservabilityQueryLatencyContext{
1423 + Context: framework.Context[EmptyLabels]{
1424 + Name: "netdata.plugin_ibm.as400_query_latency",
1425 + Family: "plugins/ibm.d/latency",
1426 + Title: "AS400 Query Latency",
1427 + Units: "ms",
1428 + Type: module.Stacked,
1429 + Priority: 146000,
1430 + UpdateEvery: 1,
1431 + Dimensions: []framework.Dimension{
1432 + {
1433 + Name: "analyze_plan_cache",
1434 + Algorithm: module.Absolute,
1435 + Mul: 1,
1436 + Div: 1000,
1437 + Precision: 1,
1438 + },
1439 + {
1440 + Name: "count_active_jobs",
1441 + Algorithm: module.Absolute,
1442 + Mul: 1,
1443 + Div: 1000,
1444 + Precision: 1,
1445 + },
1446 + {
1447 + Name: "count_disks",
1448 + Algorithm: module.Absolute,
1449 + Mul: 1,
1450 + Div: 1000,
1451 + Precision: 1,
1452 + },
1453 + {
1454 + Name: "count_http_servers",
1455 + Algorithm: module.Absolute,
1456 + Mul: 1,
1457 + Div: 1000,
1458 + Precision: 1,
1459 + },
1460 + {
1461 + Name: "count_job_queues",
1462 + Algorithm: module.Absolute,
1463 + Mul: 1,
1464 + Div: 1000,
1465 + Precision: 1,
1466 + },
1467 + {
1468 + Name: "count_message_queues",
1469 + Algorithm: module.Absolute,
1470 + Mul: 1,
1471 + Div: 1000,
1472 + Precision: 1,
1473 + },
1474 + {
1475 + Name: "count_network_interfaces",
1476 + Algorithm: module.Absolute,
1477 + Mul: 1,
1478 + Div: 1000,
1479 + Precision: 1,
1480 + },
1481 + {
1482 + Name: "count_output_queues",
1483 + Algorithm: module.Absolute,
1484 + Mul: 1,
1485 + Div: 1000,
1486 + Precision: 1,
1487 + },
1488 + {
1489 + Name: "count_subsystems",
1490 + Algorithm: module.Absolute,
1491 + Mul: 1,
1492 + Div: 1000,
1493 + Precision: 1,
1494 + },
1495 + {
1496 + Name: "detect_ibmi_version_primary",
1497 + Algorithm: module.Absolute,
1498 + Mul: 1,
1499 + Div: 1000,
1500 + Precision: 1,
1501 + },
1502 + {
1503 + Name: "detect_ibmi_version_fallback",
1504 + Algorithm: module.Absolute,
1505 + Mul: 1,
1506 + Div: 1000,
1507 + Precision: 1,
1508 + },
1509 + {
1510 + Name: "disk_instances",
1511 + Algorithm: module.Absolute,
1512 + Mul: 1,
1513 + Div: 1000,
1514 + Precision: 1,
1515 + },
1516 + {
1517 + Name: "disk_instances_enhanced",
1518 + Algorithm: module.Absolute,
1519 + Mul: 1,
1520 + Div: 1000,
1521 + Precision: 1,
1522 + },
1523 + {
1524 + Name: "disk_status",
1525 + Algorithm: module.Absolute,
1526 + Mul: 1,
1527 + Div: 1000,
1528 + Precision: 1,
1529 + },
1530 + {
1531 + Name: "http_server_info",
1532 + Algorithm: module.Absolute,
1533 + Mul: 1,
1534 + Div: 1000,
1535 + Precision: 1,
1536 + },
1537 + {
1538 + Name: "job_info",
1539 + Algorithm: module.Absolute,
1540 + Mul: 1,
1541 + Div: 1000,
1542 + Precision: 1,
1543 + },
1544 + {
1545 + Name: "job_queues",
1546 + Algorithm: module.Absolute,
1547 + Mul: 1,
1548 + Div: 1000,
1549 + Precision: 1,
1550 + },
1551 + {
1552 + Name: "memory_pools",
1553 + Algorithm: module.Absolute,
1554 + Mul: 1,
1555 + Div: 1000,
1556 + Precision: 1,
1557 + },
1558 + {
1559 + Name: "message_queue_aggregates",
1560 + Algorithm: module.Absolute,
1561 + Mul: 1,
1562 + Div: 1000,
1563 + Precision: 1,
1564 + },
1565 + {
1566 + Name: "network_connections",
1567 + Algorithm: module.Absolute,
1568 + Mul: 1,
1569 + Div: 1000,
1570 + Precision: 1,
1571 + },
1572 + {
1573 + Name: "network_interfaces",
1574 + Algorithm: module.Absolute,
1575 + Mul: 1,
1576 + Div: 1000,
1577 + Precision: 1,
1578 + },
1579 + {
1580 + Name: "output_queue_info",
1581 + Algorithm: module.Absolute,
1582 + Mul: 1,
1583 + Div: 1000,
1584 + Precision: 1,
1585 + },
1586 + {
1587 + Name: "plan_cache_summary",
1588 + Algorithm: module.Absolute,
1589 + Mul: 1,
1590 + Div: 1000,
1591 + Precision: 1,
1592 + },
1593 + {
1594 + Name: "serial_number",
1595 + Algorithm: module.Absolute,
1596 + Mul: 1,
1597 + Div: 1000,
1598 + Precision: 1,
1599 + },
1600 + {
1601 + Name: "system_activity",
1602 + Algorithm: module.Absolute,
1603 + Mul: 1,
1604 + Div: 1000,
1605 + Precision: 1,
1606 + },
1607 + {
1608 + Name: "system_model",
1609 + Algorithm: module.Absolute,
1610 + Mul: 1,
1611 + Div: 1000,
1612 + Precision: 1,
1613 + },
1614 + {
1615 + Name: "system_status",
1616 + Algorithm: module.Absolute,
1617 + Mul: 1,
1618 + Div: 1000,
1619 + Precision: 1,
1620 + },
1621 + {
1622 + Name: "temp_storage_named",
1623 + Algorithm: module.Absolute,
1624 + Mul: 1,
1625 + Div: 1000,
1626 + Precision: 1,
1627 + },
1628 + {
1629 + Name: "temp_storage_total",
1630 + Algorithm: module.Absolute,
1631 + Mul: 1,
1632 + Div: 1000,
1633 + Precision: 1,
1634 + },
1635 + {
1636 + Name: "technology_refresh_level",
1637 + Algorithm: module.Absolute,
1638 + Mul: 1,
1639 + Div: 1000,
1640 + Precision: 1,
1641 + },
1642 + {
1643 + Name: "top_active_jobs",
1644 + Algorithm: module.Absolute,
1645 + Mul: 1,
1646 + Div: 1000,
1647 + Precision: 1,
1648 + },
1649 + {
1650 + Name: "other",
1651 + Algorithm: module.Absolute,
1652 + Mul: 1,
1653 + Div: 1000,
1654 + Precision: 1,
1655 + },
1656 + },
1657 + LabelKeys: []string{},
1658 + },
1659 + },
1660 +}
1661 +
1662 // --- OutputQueue ---
1663
1664 // OutputQueueFilesValues defines the type-safe values for OutputQueue.Files context
@@ -1658,6 +1988,28 @@ func (c SystemCPUUtilizationContext) SetUpdateEvery(state *framework.CollectorSt
1988 state.SetUpdateEveryOverrideForGeneratedCode(&c.Context, nil, updateEvery)
1989 }
1990
1991 +// SystemCPUEntitledUtilizationValues defines the type-safe values for System.CPUEntitledUtilization context
1992 +type SystemCPUEntitledUtilizationValues struct {
1993 + Utilization int64
1994 +}
1995 +
1996 +// SystemCPUEntitledUtilizationContext provides type-safe operations for System.CPUEntitledUtilization context
1997 +type SystemCPUEntitledUtilizationContext struct {
1998 + framework.Context[EmptyLabels]
1999 +}
2000 +
2001 +// Set provides type-safe dimension setting for System.CPUEntitledUtilization context
2002 +func (c SystemCPUEntitledUtilizationContext) Set(state *framework.CollectorState, labels EmptyLabels, values SystemCPUEntitledUtilizationValues) {
2003 + state.SetMetricsForGeneratedCode(&c.Context, nil, map[string]int64{
2004 + "utilization": values.Utilization,
2005 + })
2006 +}
2007 +
2008 +// SetUpdateEvery sets the update interval for this instance
2009 +func (c SystemCPUEntitledUtilizationContext) SetUpdateEvery(state *framework.CollectorState, labels EmptyLabels, updateEvery int) {
2010 + state.SetUpdateEveryOverrideForGeneratedCode(&c.Context, nil, updateEvery)
2011 +}
2012 +
2013 // SystemCPUDetailsValues defines the type-safe values for System.CPUDetails context
2014 type SystemCPUDetailsValues struct {
2015 Configured int64
@@ -2177,6 +2529,7 @@ func (c SystemSystemActivityCPUUtilizationContext) SetUpdateEvery(state *framewo
2529 // System contains all metric contexts for System
2530 var System = struct {
2531 CPUUtilization SystemCPUUtilizationContext
2532 + CPUEntitledUtilization SystemCPUEntitledUtilizationContext
2533 CPUDetails SystemCPUDetailsContext
2534 CPUCapacity SystemCPUCapacityContext
2535 TotalJobs SystemTotalJobsContext
@@ -2221,6 +2574,27 @@ var System = struct {
2574 LabelKeys: []string{},
2575 },
2576 },
2577 + CPUEntitledUtilization: SystemCPUEntitledUtilizationContext{
2578 + Context: framework.Context[EmptyLabels]{
2579 + Name: "as400.cpu_utilization_entitled",
2580 + Family: "compute/cpu",
2581 + Title: "CPU Utilization (as % of entitlement)",
2582 + Units: "percentage",
2583 + Type: module.Line,
2584 + Priority: 102,
2585 + UpdateEvery: 1,
2586 + Dimensions: []framework.Dimension{
2587 + {
2588 + Name: "utilization",
2589 + Algorithm: module.Absolute,
2590 + Mul: 1,
2591 + Div: 1000,
2592 + Precision: 1,
2593 + },
2594 + },
2595 + LabelKeys: []string{},
2596 + },
2597 + },
2598 CPUDetails: SystemCPUDetailsContext{
2599 Context: framework.Context[EmptyLabels]{
2600 Name: "as400.cpu_configuration",
@@ -2894,12 +3268,14 @@ func GetAllContexts() []interface{} {
3268 &MessageQueue.Severity.Context,
3269 &NetworkInterface.Status.Context,
3270 &NetworkInterface.MTU.Context,
3271 + &Observability.QueryLatency.Context,
3272 &OutputQueue.Files.Context,
3273 &OutputQueue.Writers.Context,
3274 &OutputQueue.Status.Context,
3275 &PlanCache.Summary.Context,
3276 &Subsystem.Jobs.Context,
3277 &System.CPUUtilization.Context,
3278 + &System.CPUEntitledUtilization.Context,
3279 &System.CPUDetails.Context,
3280 &System.CPUCapacity.Context,
3281 &System.TotalJobs.Context,
src/go/plugin/ibm.d/modules/as400/helpers.go
+4 -4
@@ -51,7 +51,7 @@ func (c *Collector) collectSingleMetric(ctx context.Context, metricKey string, q
51 return nil
52 }
53
54 - err := c.doQuery(ctx, query, func(column, value string, lineEnd bool) {
54 + err := c.doQuery(ctx, metricKey, query, func(column, value string, lineEnd bool) {
55 if value != "" {
56 handler(value)
57 }
@@ -70,7 +70,7 @@ func (c *Collector) detectIBMiVersion(ctx context.Context) error {
70 var version, release string
71 versionDetected := false
72
73 - err := c.doQuery(ctx, queryIBMiVersion, func(column, value string, lineEnd bool) {
73 + err := c.doQuery(ctx, "detect_ibmi_version_primary", queryIBMiVersion, func(column, value string, lineEnd bool) {
74 switch column {
75 case "OS_NAME":
76 // ignore
@@ -88,7 +88,7 @@ func (c *Collector) detectIBMiVersion(ctx context.Context) error {
88 } else if err != nil {
89 c.Debugf("ENV_SYS_INFO query failed: %v, trying fallback method", err)
90
91 - err = c.doQuery(ctx, queryIBMiVersionDataArea, func(column, value string, lineEnd bool) {
91 + err = c.doQuery(ctx, "detect_ibmi_version_fallback", queryIBMiVersionDataArea, func(column, value string, lineEnd bool) {
92 if column == "VERSION" {
93 dataAreaValue := strings.TrimSpace(value)
94 if len(dataAreaValue) >= 6 {
@@ -131,7 +131,7 @@ func (c *Collector) collectSystemInfo(ctx context.Context) {
131 c.Debugf("detected system model: %s", c.model)
132 })
133
134 - err := c.doQuery(ctx, queryTechnologyRefresh, func(column, value string, lineEnd bool) {
134 + err := c.doQuery(ctx, "technology_refresh_level", queryTechnologyRefresh, func(column, value string, lineEnd bool) {
135 if column == "TR_LEVEL" && value != "" {
136 trLevel := strings.TrimSpace(value)
137 if trLevel != "" {
src/go/plugin/ibm.d/modules/as400/metadata.yaml
+43
@@ -451,6 +451,12 @@ modules:
451 chart_type: line
452 dimensions:
453 - name: utilization
454 + - name: as400.cpu_utilization_entitled
455 + description: CPU Utilization (as % of entitlement)
456 + unit: percentage
457 + chart_type: line
458 + dimensions:
459 + - name: utilization
460 - name: as400.cpu_configuration
461 description: CPU Configuration
462 unit: cpus
@@ -599,6 +605,43 @@ modules:
605 - name: average
606 - name: minimum
607 - name: maximum
608 + - name: netdata.plugin_ibm.as400_query_latency
609 + description: Query Collection Latency
610 + unit: ms
611 + chart_type: stacked
612 + dimensions:
613 + - name: analyze_plan_cache
614 + - name: count_active_jobs
615 + - name: count_disks
616 + - name: count_http_servers
617 + - name: count_job_queues
618 + - name: count_message_queues
619 + - name: count_network_interfaces
620 + - name: count_output_queues
621 + - name: count_subsystems
622 + - name: detect_ibmi_version_primary
623 + - name: detect_ibmi_version_fallback
624 + - name: disk_instances
625 + - name: disk_instances_enhanced
626 + - name: disk_status
627 + - name: http_server_info
628 + - name: job_info
629 + - name: job_queues
630 + - name: memory_pools
631 + - name: message_queue_aggregates
632 + - name: network_connections
633 + - name: network_interfaces
634 + - name: output_queue_info
635 + - name: plan_cache_summary
636 + - name: serial_number
637 + - name: system_activity
638 + - name: system_model
639 + - name: system_status
640 + - name: temp_storage_named
641 + - name: temp_storage_total
642 + - name: technology_refresh_level
643 + - name: top_active_jobs
644 + - name: other
645 - name: tempstoragebucket
646 description: These metrics refer to tempstoragebucket instances.
647 labels:
src/go/plugin/ibm.d/modules/as400/metrics.go
+6 -3
@@ -7,9 +7,10 @@ package as400
7
8 type metricsData struct {
9 // CPU metrics from SYSTEM_STATUS()
10 - CPUPercentage int64 `stm:"cpu_percentage"` // AVERAGE_CPU_UTILIZATION
11 - CurrentCPUCapacity int64 `stm:"current_cpu_capacity"` // CURRENT_CPU_CAPACITY
12 - ConfiguredCPUs int64 `stm:"configured_cpus"` // CONFIGURED_CPUS
10 + CPUPercentage int64 `stm:"cpu_percentage"` // AVERAGE_CPU_UTILIZATION
11 + CurrentCPUCapacity int64 `stm:"current_cpu_capacity"` // CURRENT_CPU_CAPACITY
12 + ConfiguredCPUs int64 `stm:"configured_cpus"` // CONFIGURED_CPUS
13 + EntitledCPUPercentage int64 `stm:"entitled_cpu_percentage"`
14
15 // Memory metrics from SYSTEM_STATUS()
16 MainStorageSize int64 `stm:"main_storage_size"` // MAIN_STORAGE_SIZE (KB)
@@ -76,6 +77,8 @@ type metricsData struct {
77 httpServers map[string]httpServerInstanceMetrics
78 planCache map[string]planCacheInstanceMetrics
79 systemActivity systemActivityMetrics
80 +
81 + queryLatencies map[string]int64 `stm:"-"`
82 }
83
84 // Per-instance metric structures for stm conversion