diff --git a/LICENSE b/LICENSE index 64624a7ad78..c71ceabecbc 100644 --- a/LICENSE +++ b/LICENSE @@ -309,4 +309,12 @@ Files (in server/monitor/src/main/resources/): Copyright (c) 2008-2024 SpryMedia Ltd Licensed under the MIT license (see above) +## DataTables ColReorder 1.7.0 (https://datatables.net) + +Files (in server/monitor/src/main/resources/): +* org/apache/accumulo/monitor/resources/external/datatables/**/* + + Copyright (c) 2010-2015 SpryMedia Limited + Licensed under the MIT license (see above) + ********** diff --git a/core/src/main/java/org/apache/accumulo/core/metrics/Metric.java b/core/src/main/java/org/apache/accumulo/core/metrics/Metric.java index 4898ff4eb8b..633a149bd89 100644 --- a/core/src/main/java/org/apache/accumulo/core/metrics/Metric.java +++ b/core/src/main/java/org/apache/accumulo/core/metrics/Metric.java @@ -18,316 +18,408 @@ */ package org.apache.accumulo.core.metrics; +import static org.apache.accumulo.core.metrics.Metric.MonitorCssClass.BYTES; +import static org.apache.accumulo.core.metrics.Metric.MonitorCssClass.DATE_END; +import static org.apache.accumulo.core.metrics.Metric.MonitorCssClass.DATE_START; +import static org.apache.accumulo.core.metrics.Metric.MonitorCssClass.DURATION; +import static org.apache.accumulo.core.metrics.Metric.MonitorCssClass.IDLE_STATE; +import static org.apache.accumulo.core.metrics.Metric.MonitorCssClass.MEMORY_STATE; +import static org.apache.accumulo.core.metrics.Metric.MonitorCssClass.NUMBER; +import static org.apache.accumulo.core.metrics.Metric.MonitorCssClass.PERCENT; + import java.util.HashMap; import java.util.Map; +import java.util.Set; +import org.apache.accumulo.core.client.admin.servers.ServerId; import org.apache.accumulo.core.fate.FateExecutorMetrics; public enum Metric { + // General Server Metrics SERVER_IDLE("accumulo.server.idle", MetricType.GAUGE, "Indicates if the server is idle or not. The value will be 1 when idle and 0 when not idle.", - MetricDocSection.GENERAL_SERVER), + MetricDocSection.GENERAL_SERVER, "Server Idle", null, IDLE_STATE), LOW_MEMORY("accumulo.detected.low.memory", MetricType.GAUGE, "Reports 1 when process memory usage is above the threshold, reports 0 when memory is okay.", - MetricDocSection.GENERAL_SERVER), + MetricDocSection.GENERAL_SERVER, "Low Memory", null, MEMORY_STATE), THRIFT_IDLE("accumulo.thrift.idle", MetricType.DISTRIBUTION_SUMMARY, - "Time waiting to execute an RPC request.", MetricDocSection.GENERAL_SERVER), + "Time waiting to execute an RPC request.", MetricDocSection.GENERAL_SERVER, + "Thrift Idle Time", null, NUMBER), THRIFT_EXECUTE("accumulo.thrift.execute", MetricType.DISTRIBUTION_SUMMARY, - "Time to execute an RPC request.", MetricDocSection.GENERAL_SERVER), + "Time to execute an RPC request.", MetricDocSection.GENERAL_SERVER, "Thrift Execution Time", + null, NUMBER), // Compactor Metrics COMPACTION_ROOT_SVC_ERRORS("accumulo.compaction.svc.root.misconfigured", MetricType.GAUGE, "A value of 1 indicates a misconfiguration in the compaction service, while a value of 0 indicates that the configuration is valid.", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Root Compaction Svc Errors", null, NUMBER), COMPACTION_META_SVC_ERRORS("accumulo.compaction.svc.meta.misconfigured", MetricType.GAUGE, "A value of 1 indicates a misconfiguration in the compaction service, while a value of 0 indicates that the configuration is valid.", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Meta Compaction Svc Errors", null, NUMBER), COMPACTION_USER_SVC_ERRORS("accumulo.compaction.svc.user.misconfigured", MetricType.GAUGE, "A value of 1 indicates a misconfiguration in the compaction service, while a value of 0 indicates that the configuration is valid.", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "User Compaction Svc Errors", null, NUMBER), COMPACTOR_MAJC_CANCELLED("accumulo.compaction.majc.cancelled", MetricType.FUNCTION_COUNTER, - "Number compactions that have been cancelled on this compactor", MetricDocSection.COMPACTION), + "Number compactions that have been cancelled on this compactor", MetricDocSection.COMPACTION, + "Majc Cancelled", null, NUMBER), COMPACTOR_MAJC_COMPLETED("accumulo.compaction.majc.completed", MetricType.FUNCTION_COUNTER, - "Number compactions that have succeeded on this compactor", MetricDocSection.COMPACTION), + "Number compactions that have succeeded on this compactor", MetricDocSection.COMPACTION, + "Majc Completed", null, NUMBER), COMPACTOR_MAJC_FAILED("accumulo.compaction.majc.failed", MetricType.FUNCTION_COUNTER, - "Number compactions that have failed on this compactor", MetricDocSection.COMPACTION), + "Number compactions that have failed on this compactor", MetricDocSection.COMPACTION, + "Majc Failed", null, NUMBER), COMPACTOR_MAJC_FAILURES_CONSECUTIVE("accumulo.compaction.majc.failures.consecutive", MetricType.GAUGE, "Number of consecutive compaction failures. Resets to zero on a successful compaction", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Majc Consecutive Failures", null, NUMBER), COMPACTOR_MAJC_FAILURES_TERMINATION("accumulo.compaction.process.terminated", MetricType.FUNCTION_COUNTER, "Will report 1 if the Compactor terminates due to consecutive failures, else 0. Emitting this metric is a best effort before the process terminates", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Majc Consecutive Failure Termination", null, NUMBER), COMPACTOR_MAJC_IN_PROGRESS("accumulo.compaction.majc.in_progress", MetricType.GAUGE, "Indicator of whether a compaction is in-progress (value: 1) or not (value: 0). An" + " in-progress compaction could also be stuck.", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Majc In Progress", null, NUMBER), COMPACTOR_MAJC_STUCK("accumulo.compaction.majc.stuck", MetricType.LONG_TASK_TIMER, - "Number and duration of stuck major compactions.", MetricDocSection.COMPACTION), + "Number and duration of stuck major compactions.", MetricDocSection.COMPACTION, "Majc Stuck", + null, NUMBER), COMPACTOR_MINC_STUCK("accumulo.compaction.minc.stuck", MetricType.LONG_TASK_TIMER, - "Number and duration of stuck minor compactions.", MetricDocSection.COMPACTION), + "Number and duration of stuck minor compactions.", MetricDocSection.COMPACTION, "Minc Stuck", + null, NUMBER), COMPACTOR_ENTRIES_READ("accumulo.compaction.entries.read", MetricType.FUNCTION_COUNTER, "Number of entries read by all compactions that have run on this compactor (majc) or tserver (minc).", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Compaction Entries Read", null, NUMBER), COMPACTOR_ENTRIES_WRITTEN("accumulo.compaction.entries.written", MetricType.FUNCTION_COUNTER, "Number of entries written by all compactions that have run on this compactor (majc) or tserver (minc).", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Compaction Entries Written", null, NUMBER), COMPACTOR_JOB_PRIORITY_QUEUES("accumulo.compaction.queue.count", MetricType.GAUGE, - "Number of priority queues for compaction jobs.", MetricDocSection.COMPACTION), + "Number of priority queues for compaction jobs.", MetricDocSection.COMPACTION, + "Compaction Queue Count", null, NUMBER), COMPACTOR_JOB_PRIORITY_QUEUE_JOBS_DEQUEUED("accumulo.compaction.queue.jobs.dequeued", - MetricType.GAUGE, "Count of dequeued jobs.", MetricDocSection.COMPACTION), + MetricType.GAUGE, "Count of dequeued jobs.", MetricDocSection.COMPACTION, + "Compaction Jobs Dequeued", null, NUMBER), COMPACTOR_JOB_PRIORITY_QUEUE_JOBS_QUEUED("accumulo.compaction.queue.jobs.queued", - MetricType.GAUGE, "Count of queued jobs.", MetricDocSection.COMPACTION), + MetricType.GAUGE, "Count of queued jobs.", MetricDocSection.COMPACTION, + "Compaction Jobs Queued", null, NUMBER), COMPACTOR_JOB_PRIORITY_QUEUE_JOBS_SIZE("accumulo.compaction.queue.jobs.size", MetricType.GAUGE, - "Size of queued jobs in bytes.", MetricDocSection.COMPACTION), + "Size of queued jobs in bytes.", MetricDocSection.COMPACTION, "Compaction Queued Jobs Size", + null, NUMBER), COMPACTOR_JOB_PRIORITY_QUEUE_JOBS_REJECTED("accumulo.compaction.queue.jobs.rejected", - MetricType.GAUGE, "Count of rejected jobs.", MetricDocSection.COMPACTION), + MetricType.GAUGE, "Count of rejected jobs.", MetricDocSection.COMPACTION, + "Compaction Queue Jobs Rejected", null, NUMBER), COMPACTOR_JOB_PRIORITY_QUEUE_JOBS_PRIORITY("accumulo.compaction.queue.jobs.priority", - MetricType.GAUGE, "Lowest priority queued job.", MetricDocSection.COMPACTION), + MetricType.GAUGE, "Lowest priority queued job.", MetricDocSection.COMPACTION, + "Compaction Queue Lowest Priority", null, NUMBER), COMPACTOR_JOB_PRIORITY_QUEUE_JOBS_MIN_AGE("accumulo.compaction.queue.jobs.min.age", MetricType.GAUGE, "Minimum age of currently queued jobs in seconds.", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Compaction Queue Min Job Age", null, NUMBER), COMPACTOR_JOB_PRIORITY_QUEUE_JOBS_MAX_AGE("accumulo.compaction.queue.jobs.max.age", MetricType.GAUGE, "Maximum age of currently queued jobs in seconds.", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Compaction Queue Max Job Age", null, NUMBER), COMPACTOR_JOB_PRIORITY_QUEUE_JOBS_AVG_AGE("accumulo.compaction.queue.jobs.avg.age", MetricType.GAUGE, "Average age of currently queued jobs in seconds.", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Compaction Queue Avg Job Age", null, NUMBER), COMPACTOR_JOB_PRIORITY_QUEUE_JOBS_POLL_TIMER("accumulo.compaction.queue.jobs.exit.time", MetricType.TIMER, "Tracks time a job spent in the queue before exiting the queue.", - MetricDocSection.COMPACTION), + MetricDocSection.COMPACTION, "Compaction Queue Job Time Queued", null, NUMBER), // Fate Metrics FATE_TYPE_IN_PROGRESS("accumulo.fate.ops.in.progress.by.type", MetricType.GAUGE, "Number of FATE operations in progress. The op type is designated by the `op.type` tag.", - MetricDocSection.FATE), + MetricDocSection.FATE, "Fate Ops In Progess Count", null, NUMBER), FATE_OPS("accumulo.fate.ops", MetricType.GAUGE, - "Number of all the current FATE ops in any state.", MetricDocSection.FATE), + "Number of all the current FATE ops in any state.", MetricDocSection.FATE, + "Current Fate Ops Count", null, NUMBER), FATE_OPS_ACTIVITY("accumulo.fate.ops.activity", MetricType.GAUGE, "Count of the total number of times fate operations are added, updated, and removed.", - MetricDocSection.FATE), + MetricDocSection.FATE, "Total Fate Ops Count", null, NUMBER), FATE_ERRORS("accumulo.fate.errors", MetricType.GAUGE, "Count of errors that occurred when attempting to gather fate metrics.", - MetricDocSection.FATE), + MetricDocSection.FATE, "Fate Errors Count", null, NUMBER), FATE_TX("accumulo.fate.tx", MetricType.GAUGE, "Count of FATE operations in a certain state. The state is now in a tag " + "(e.g., state=new, state=in.progress, state=failed, etc.).", - MetricDocSection.FATE), + MetricDocSection.FATE, "Fate Ops State Count", null, NUMBER), FATE_OPS_THREADS_INACTIVE("accumulo.fate.ops.threads.inactive", MetricType.GAUGE, "Keeps track of the number of idle threads (not working on a fate operation) in the thread " + "pool. The pool name can be found in the " + FateExecutorMetrics.POOL_NAME_TAG_KEY + " tag. The fate instance type can be found in the " + FateExecutorMetrics.INSTANCE_TYPE_TAG_KEY + " tag.", - MetricDocSection.FATE), + MetricDocSection.FATE, "Fate Threads Inactive", null, NUMBER), FATE_OPS_THREADS_TOTAL("accumulo.fate.ops.threads.total", MetricType.GAUGE, "Keeps track of the total number of threads in the thread pool. The pool name can be found in the " + FateExecutorMetrics.POOL_NAME_TAG_KEY + " tag. The fate instance type can be found in the " + FateExecutorMetrics.INSTANCE_TYPE_TAG_KEY + " tag.", - MetricDocSection.FATE), + MetricDocSection.FATE, "Fate Threads Total", null, NUMBER), // Garbage Collection Metrics GC_STARTED("accumulo.gc.started", MetricType.GAUGE, "Timestamp GC file collection cycle started.", - MetricDocSection.GARBAGE_COLLECTION), + MetricDocSection.GARBAGE_COLLECTION, "GC File Cycle Start Time", null, DATE_START), GC_FINISHED("accumulo.gc.finished", MetricType.GAUGE, "Timestamp GC file collect cycle finished.", - MetricDocSection.GARBAGE_COLLECTION), + MetricDocSection.GARBAGE_COLLECTION, "GC File Cycle End Time", null, DATE_END), GC_CANDIDATES("accumulo.gc.candidates", MetricType.GAUGE, - "Number of files that are candidates for deletion.", MetricDocSection.GARBAGE_COLLECTION), + "Number of files that are candidates for deletion.", MetricDocSection.GARBAGE_COLLECTION, + "GC File Deletion Candidate Count", null, NUMBER), GC_IN_USE("accumulo.gc.in.use", MetricType.GAUGE, "Number of candidate files still in use.", - MetricDocSection.GARBAGE_COLLECTION), + MetricDocSection.GARBAGE_COLLECTION, "GC File Deletion Candidates In Use", null, NUMBER), GC_DELETED("accumulo.gc.deleted", MetricType.GAUGE, "Number of candidate files deleted.", - MetricDocSection.GARBAGE_COLLECTION), + MetricDocSection.GARBAGE_COLLECTION, "GC File Candidates Deleted", null, NUMBER), GC_ERRORS("accumulo.gc.errors", MetricType.GAUGE, "Number of candidate deletion errors.", - MetricDocSection.GARBAGE_COLLECTION), + MetricDocSection.GARBAGE_COLLECTION, "GC File Candidate Deletion Errors", null, NUMBER), GC_WAL_STARTED("accumulo.gc.wal.started", MetricType.GAUGE, - "Timestamp GC WAL collection cycle started.", MetricDocSection.GARBAGE_COLLECTION), + "Timestamp GC WAL collection cycle started.", MetricDocSection.GARBAGE_COLLECTION, + "GC WAL Cycle Start Time", null, DATE_START), GC_WAL_FINISHED("accumulo.gc.wal.finished", MetricType.GAUGE, - "Timestamp GC WAL collect cycle finished.", MetricDocSection.GARBAGE_COLLECTION), + "Timestamp GC WAL collect cycle finished.", MetricDocSection.GARBAGE_COLLECTION, + "GC WAL Cycle End Time", null, DATE_END), GC_WAL_CANDIDATES("accumulo.gc.wal.candidates", MetricType.GAUGE, - "Number of files that are candidates for deletion.", MetricDocSection.GARBAGE_COLLECTION), + "Number of files that are candidates for deletion.", MetricDocSection.GARBAGE_COLLECTION, + "GC WAL Deletion Candidate Count", null, NUMBER), GC_WAL_IN_USE("accumulo.gc.wal.in.use", MetricType.GAUGE, - "Number of wal file candidates that are still in use.", MetricDocSection.GARBAGE_COLLECTION), + "Number of wal file candidates that are still in use.", MetricDocSection.GARBAGE_COLLECTION, + "GC WAL Deletion Candidaets In Use", null, NUMBER), GC_WAL_DELETED("accumulo.gc.wal.deleted", MetricType.GAUGE, - "Number of candidate wal files deleted.", MetricDocSection.GARBAGE_COLLECTION), + "Number of candidate wal files deleted.", MetricDocSection.GARBAGE_COLLECTION, + "GC WAL Candidates Deleted", null, NUMBER), GC_WAL_ERRORS("accumulo.gc.wal.errors", MetricType.GAUGE, - "Number candidate wal file deletion errors.", MetricDocSection.GARBAGE_COLLECTION), + "Number candidate wal file deletion errors.", MetricDocSection.GARBAGE_COLLECTION, + "GC WAL Candidate Deletion Errors", null, NUMBER), GC_POST_OP_DURATION("accumulo.gc.post.op.duration", MetricType.GAUGE, "GC metadata table post operation duration in milliseconds.", - MetricDocSection.GARBAGE_COLLECTION), + MetricDocSection.GARBAGE_COLLECTION, "GC Post Ops Duration", null, DURATION), GC_RUN_CYCLE("accumulo.gc.run.cycle", MetricType.GAUGE, "Count of gc cycle runs. Value is reset on process start.", - MetricDocSection.GARBAGE_COLLECTION), + MetricDocSection.GARBAGE_COLLECTION, "GC Cycle Count", null, NUMBER), // Tablet Server Metrics TSERVER_ENTRIES("accumulo.tserver.entries", MetricType.GAUGE, - "Number of entries assigned to a TabletServer.", MetricDocSection.TABLET_SERVER), + "Number of entries assigned to a TabletServer.", MetricDocSection.TABLET_SERVER, + "Entries Assigned", null, NUMBER), TSERVER_MEM_ENTRIES("accumulo.tserver.entries.mem", MetricType.GAUGE, - "Number of entries in memory.", MetricDocSection.TABLET_SERVER), + "Number of entries in memory.", MetricDocSection.TABLET_SERVER, "Entries In Memory", null, + NUMBER), TSERVER_MINC_QUEUED("accumulo.minc.queued", MetricType.GAUGE, - "Number of queued minor compactions.", MetricDocSection.COMPACTION), + "Number of queued minor compactions.", MetricDocSection.COMPACTION, "Minc Queued", null, + NUMBER), TSERVER_MINC_RUNNING("accumulo.minc.running", MetricType.GAUGE, - "Number of active minor compactions.", MetricDocSection.COMPACTION), + "Number of active minor compactions.", MetricDocSection.COMPACTION, "Minc Running", null, + NUMBER), TSERVER_MINC_TOTAL("accumulo.minc.total", MetricType.GAUGE, - "Total number of minor compactions performed.", MetricDocSection.COMPACTION), + "Total number of minor compactions performed.", MetricDocSection.COMPACTION, "Minc Completed", + null, NUMBER), TSERVER_TABLETS_ONLINE("accumulo.tablets.online", MetricType.GAUGE, "Number of online tablets.", - MetricDocSection.TABLET_SERVER), + MetricDocSection.TABLET_SERVER, "Tablets Online", null, NUMBER), TSERVER_TABLETS_LONG_ASSIGNMENTS("accumulo.tablets.assignments.warning", MetricType.GAUGE, "Number of tablet assignments that are taking longer than the configured warning duration.", - MetricDocSection.TABLET_SERVER), + MetricDocSection.TABLET_SERVER, "Tablet Assignments Overdue", null, NUMBER), TSERVER_TABLETS_OPENING("accumulo.tablets.opening", MetricType.GAUGE, - "Number of opening tablets.", MetricDocSection.TABLET_SERVER), + "Number of opening tablets.", MetricDocSection.TABLET_SERVER, "Tablets Opening", null, + NUMBER), TSERVER_TABLETS_UNOPENED("accumulo.tablets.unopened", MetricType.GAUGE, - "Number of unopened tablets.", MetricDocSection.TABLET_SERVER), + "Number of unopened tablets.", MetricDocSection.TABLET_SERVER, "Tablets Unopened", null, + NUMBER), TSERVER_TABLETS_FILES("accumulo.tablets.files", MetricType.GAUGE, "Number of files per tablet.", - MetricDocSection.TABLET_SERVER), + MetricDocSection.TABLET_SERVER, "Tablet File Avg", null, NUMBER), TSERVER_INGEST_ENTRIES("accumulo.ingest.entries", MetricType.GAUGE, "Ingest entry (a key/value) count. The rate can be derived from this metric.", - MetricDocSection.TABLET_SERVER), + MetricDocSection.TABLET_SERVER, "Entries Ingested", null, NUMBER), TSERVER_INGEST_BYTES("accumulo.ingest.bytes", MetricType.GAUGE, "Ingest byte count. The rate can be derived from this metric.", - MetricDocSection.TABLET_SERVER), + MetricDocSection.TABLET_SERVER, "Bytes Ingested", null, BYTES), TSERVER_HOLD("accumulo.ingest.hold", MetricType.GAUGE, - "Duration for which commits have been held in milliseconds.", MetricDocSection.TABLET_SERVER), + "Duration for which commits have been held in milliseconds.", MetricDocSection.TABLET_SERVER, + "Ingest Commit Hold Time", null, NUMBER), TSERVER_TABLETS_ONLINE_ONDEMAND("accumulo.tablets.ondemand.online", MetricType.GAUGE, - "Number of online on-demand tablets", MetricDocSection.TABLET_SERVER), + "Number of online on-demand tablets", MetricDocSection.TABLET_SERVER, + "Online On-Demand Tablets", null, NUMBER), TSERVER_TABLETS_ONDEMAND_UNLOADED_FOR_MEM("accumulo.tablets.ondemand.unloaded.lowmem", MetricType.GAUGE, "Number of online on-demand tablets unloaded due to low memory", - MetricDocSection.TABLET_SERVER), + MetricDocSection.TABLET_SERVER, "On-Demand Tablets Unloaded For Memory", null, NUMBER), // Scan Server Metrics SCAN_RESERVATION_TOTAL_TIMER("accumulo.scan.reservation.total.timer", MetricType.TIMER, - "Time to reserve a tablet's files for scan.", MetricDocSection.SCAN_SERVER), + "Time to reserve a tablet's files for scan.", MetricDocSection.SCAN_SERVER, + "Scan Reservation Total Time", null, NUMBER), SCAN_RESERVATION_WRITEOUT_TIMER("accumulo.scan.reservation.writeout.timer", MetricType.TIMER, - "Time to write out a tablets file reservations for scan.", MetricDocSection.SCAN_SERVER), + "Time to write out a tablets file reservations for scan.", MetricDocSection.SCAN_SERVER, + "Scan Reservation Write Time", null, NUMBER), SCAN_RESERVATION_CONFLICT_COUNTER("accumulo.scan.reservation.conflict.count", MetricType.COUNTER, "Count of instances where file reservation attempts for scans encountered conflicts.", - MetricDocSection.SCAN_SERVER), + MetricDocSection.SCAN_SERVER, "Scan Reservation Conflicts", null, NUMBER), SCAN_TABLET_METADATA_CACHE("accumulo.scan.tablet.metadata.cache", MetricType.CACHE, - "Scan server tablet cache metrics.", MetricDocSection.SCAN_SERVER), + "Scan server tablet cache metrics.", MetricDocSection.SCAN_SERVER, "Scan Server Tablet Cache", + null, NUMBER), // Scan Metrics SCAN_BUSY_TIMEOUT_COUNT("accumulo.scan.busy.timeout.count", MetricType.COUNTER, - "Count of the scans where a busy timeout happened.", MetricDocSection.SCAN), + "Count of the scans where a busy timeout happened.", MetricDocSection.SCAN, "Scan Busy Count", + null, NUMBER), SCAN_TIMES("accumulo.scan.times", MetricType.TIMER, "Scan session lifetime (creation to close).", - MetricDocSection.SCAN), + MetricDocSection.SCAN, "Scan Session Total Time", null, NUMBER), SCAN_OPEN_FILES("accumulo.scan.files.open", MetricType.GAUGE, "Number of files open for scans.", - MetricDocSection.SCAN), - SCAN_RESULTS("accumulo.scan.result", MetricType.GAUGE, "Results per scan.", - MetricDocSection.SCAN), + MetricDocSection.SCAN, "Scan Files Open", null, NUMBER), + SCAN_RESULTS("accumulo.scan.result", MetricType.GAUGE, "Results per scan.", MetricDocSection.SCAN, + "Scan Result Count", null, NUMBER), SCAN_YIELDS("accumulo.scan.yields", MetricType.GAUGE, "Counts scans that have yielded.", - MetricDocSection.SCAN), + MetricDocSection.SCAN, "Scan Yield Count", null, NUMBER), SCAN_START("accumulo.scan.start", MetricType.COUNTER, - "Number of calls to start a scan or multiscan.", MetricDocSection.SCAN), + "Number of calls to start a scan or multiscan.", MetricDocSection.SCAN, "Scan Start Count", + null, NUMBER), SCAN_CONTINUE("accumulo.scan.continue", MetricType.COUNTER, - "Number of calls to continue a scan or multiscan.", MetricDocSection.SCAN), + "Number of calls to continue a scan or multiscan.", MetricDocSection.SCAN, + "Scan Continue Count", null, NUMBER), SCAN_CLOSE("accumulo.scan.close", MetricType.COUNTER, - "Number of calls to close a scan or multiscan.", MetricDocSection.SCAN), + "Number of calls to close a scan or multiscan.", MetricDocSection.SCAN, "Scan Close Count", + null, NUMBER), SCAN_QUERIES("accumulo.scan.queries", MetricType.GAUGE, "Number of queries made during scans.", - MetricDocSection.SCAN), + MetricDocSection.SCAN, "Tablet Lookup Count", null, NUMBER), SCAN_SCANNED_ENTRIES("accumulo.scan.query.scanned.entries", MetricType.GAUGE, - "Count of scanned entries. The rate can be derived from this metric.", MetricDocSection.SCAN), + "Count of scanned entries. The rate can be derived from this metric.", MetricDocSection.SCAN, + "Scanned Entry Count", null, NUMBER), SCAN_QUERY_SCAN_RESULTS("accumulo.scan.query.results", MetricType.GAUGE, - "Query count. The rate can be derived from this metric.", MetricDocSection.SCAN), + "Query count. The rate can be derived from this metric.", MetricDocSection.SCAN, + "Returned Entry Count", null, NUMBER), SCAN_QUERY_SCAN_RESULTS_BYTES("accumulo.scan.query.results.bytes", MetricType.GAUGE, - "Query byte count. The rate can be derived from this metric.", MetricDocSection.SCAN), + "Query byte count. The rate can be derived from this metric.", MetricDocSection.SCAN, + "Returned Bytes Count", null, BYTES), SCAN_PAUSED_FOR_MEM("accumulo.scan.paused.for.memory", MetricType.COUNTER, - "Count of scans paused due to server being low on memory.", MetricDocSection.SCAN), + "Count of scans paused due to server being low on memory.", MetricDocSection.SCAN, + "Scans Paused For Low Memory", null, NUMBER), SCAN_RETURN_FOR_MEM("accumulo.scan.return.early.for.memory", MetricType.COUNTER, "Count of scans that returned results early due to server being low on memory.", - MetricDocSection.SCAN), + MetricDocSection.SCAN, "Scans Returned Early For Low Memory", null, NUMBER), SCAN_ZOMBIE_THREADS("accumulo.scan.zombie.threads", MetricType.GAUGE, - "Number of scan threads that have no associated client session.", MetricDocSection.SCAN), + "Number of scan threads that have no associated client session.", MetricDocSection.SCAN, + "Scan Zombie Thread Count", null, NUMBER), // Major Compaction Metrics MAJC_PAUSED("accumulo.compaction.majc.paused", MetricType.COUNTER, - "Number of paused major compactions.", MetricDocSection.COMPACTOR), + "Number of paused major compactions.", MetricDocSection.COMPACTOR, "Majc Paused", null, + NUMBER), // Minor Compaction Metrics MINC_QUEUED("accumulo.compaction.minc.queued", MetricType.TIMER, - "Queued minor compactions time queued.", MetricDocSection.COMPACTION), + "Queued minor compactions time queued.", MetricDocSection.COMPACTION, "Minc Queued", null, + NUMBER), MINC_RUNNING("accumulo.compaction.minc.running", MetricType.TIMER, - "Minor compactions time active.", MetricDocSection.COMPACTION), + "Minor compactions time active.", MetricDocSection.COMPACTION, "Minc Running", null, NUMBER), MINC_PAUSED("accumulo.compaction.minc.paused", MetricType.COUNTER, - "Number of paused minor compactions.", MetricDocSection.COMPACTION), + "Number of paused minor compactions.", MetricDocSection.COMPACTION, "Minc Paused", null, + NUMBER), // Updates (Ingest) Metrics UPDATE_ERRORS("accumulo.updates.error", MetricType.GAUGE, "Count of errors during tablet updates. Type/reason for error is stored in the `type` tag (e.g., type=permission, type=unknown.tablet, type=constraint.violation).", - MetricDocSection.TABLET_SERVER), + MetricDocSection.TABLET_SERVER, "Ingest Errors", null, NUMBER), UPDATE_LOCK("accumulo.updates.lock", MetricType.TIMER, "Average time taken for conditional mutation to get a row lock.", - MetricDocSection.TABLET_SERVER), + MetricDocSection.TABLET_SERVER, "Conditional Mutation Row Lock Wait Time", null, NUMBER), UPDATE_CHECK("accumulo.updates.check", MetricType.TIMER, "Average time taken for conditional mutation to check conditions.", - MetricDocSection.TABLET_SERVER), + MetricDocSection.TABLET_SERVER, "Conditional Mutation Condition Check Time", null, NUMBER), UPDATE_COMMIT("accumulo.updates.commit", MetricType.TIMER, - "Average time taken to commit a mutation.", MetricDocSection.TABLET_SERVER), + "Average time taken to commit a mutation.", MetricDocSection.TABLET_SERVER, + "Mutation Commit Avg Total Time", null, NUMBER), UPDATE_COMMIT_PREP("accumulo.updates.commit.prep", MetricType.TIMER, - "Average time taken to prepare to commit a single mutation.", MetricDocSection.TABLET_SERVER), + "Average time taken to prepare to commit a single mutation.", MetricDocSection.TABLET_SERVER, + "Mutation Commit Avg Prep Time", null, NUMBER), UPDATE_WALOG_WRITE("accumulo.updates.walog.write", MetricType.TIMER, - "Time taken to write a batch of mutations to WAL.", MetricDocSection.TABLET_SERVER), + "Time taken to write a batch of mutations to WAL.", MetricDocSection.TABLET_SERVER, + "WAL Write Time", null, NUMBER), UPDATE_MUTATION_ARRAY_SIZE("accumulo.updates.mutation.arrays.size", MetricType.DISTRIBUTION_SUMMARY, "Batch size of mutations from client.", - MetricDocSection.TABLET_SERVER), + MetricDocSection.TABLET_SERVER, "Mutation Batch Size", null, NUMBER), // Block Cache Metrics BLOCKCACHE_INDEX_HITCOUNT("accumulo.blockcache.index.hitcount", MetricType.FUNCTION_COUNTER, - "Index block cache hit count.", MetricDocSection.BLOCK_CACHE), + "Index block cache hit count.", MetricDocSection.BLOCK_CACHE, "Index Block Cache Hit Count", + null, NUMBER), BLOCKCACHE_INDEX_REQUESTCOUNT("accumulo.blockcache.index.requestcount", - MetricType.FUNCTION_COUNTER, "Index block cache request count.", - MetricDocSection.BLOCK_CACHE), + MetricType.FUNCTION_COUNTER, "Index block cache request count.", MetricDocSection.BLOCK_CACHE, + "Index Block Cache Request Count", null, NUMBER), BLOCKCACHE_INDEX_EVICTIONCOUNT("accumulo.blockcache.index.evictioncount", MetricType.FUNCTION_COUNTER, "Index block cache eviction count.", - MetricDocSection.BLOCK_CACHE), + MetricDocSection.BLOCK_CACHE, "Index Block Cache Eviction Count", null, NUMBER), BLOCKCACHE_DATA_HITCOUNT("accumulo.blockcache.data.hitcount", MetricType.FUNCTION_COUNTER, - "Data block cache hit count.", MetricDocSection.BLOCK_CACHE), + "Data block cache hit count.", MetricDocSection.BLOCK_CACHE, "Data Block Cache Hit Count", + null, NUMBER), BLOCKCACHE_DATA_REQUESTCOUNT("accumulo.blockcache.data.requestcount", MetricType.FUNCTION_COUNTER, - "Data block cache request count.", MetricDocSection.BLOCK_CACHE), + "Data block cache request count.", MetricDocSection.BLOCK_CACHE, + "Data Block Cache Request Count", null, NUMBER), BLOCKCACHE_DATA_EVICTIONCOUNT("accumulo.blockcache.data.evictioncount", - MetricType.FUNCTION_COUNTER, "Data block cache eviction count.", - MetricDocSection.BLOCK_CACHE), + MetricType.FUNCTION_COUNTER, "Data block cache eviction count.", MetricDocSection.BLOCK_CACHE, + "Data Block Cache Eviction Count", null, NUMBER), BLOCKCACHE_SUMMARY_HITCOUNT("accumulo.blockcache.summary.hitcount", MetricType.FUNCTION_COUNTER, - "Summary block cache hit count.", MetricDocSection.BLOCK_CACHE), + "Summary block cache hit count.", MetricDocSection.BLOCK_CACHE, + "Summary Block Cache Hit Count", null, NUMBER), BLOCKCACHE_SUMMARY_REQUESTCOUNT("accumulo.blockcache.summary.requestcount", MetricType.FUNCTION_COUNTER, "Summary block cache request count.", - MetricDocSection.BLOCK_CACHE), + MetricDocSection.BLOCK_CACHE, "Summary Block Cache Request Count", null, NUMBER), BLOCKCACHE_SUMMARY_EVICTIONCOUNT("accumulo.blockcache.summary.evictioncount", MetricType.FUNCTION_COUNTER, "Summary block cache eviction count.", - MetricDocSection.BLOCK_CACHE), + MetricDocSection.BLOCK_CACHE, "Summary Block Cache Eviction Count", null, NUMBER), // Manager Metrics MANAGER_BALANCER_MIGRATIONS_NEEDED("accumulo.balancer.migrations.needed", MetricType.GAUGE, "The number of migrations that need to complete before the system is balanced.", - MetricDocSection.MANAGER), + MetricDocSection.MANAGER, "Balancer Migrations Needed", null, NUMBER), MANAGER_ROOT_TGW_ERRORS("accumulo.tabletmgmt.root.errors", MetricType.GAUGE, "Error count encountered by the TabletGroupWatcher for the ROOT data level.", - MetricDocSection.MANAGER), + MetricDocSection.MANAGER, "Root Tablet Watcher Errors", null, NUMBER), MANAGER_META_TGW_ERRORS("accumulo.tabletmgmt.meta.errors", MetricType.GAUGE, "Error count encountered by the TabletGroupWatcher for the META data level.", - MetricDocSection.MANAGER), + MetricDocSection.MANAGER, "Meta Tablet Watcher Errors", null, NUMBER), MANAGER_USER_TGW_ERRORS("accumulo.tabletmgmt.user.errors", MetricType.GAUGE, "Error count encountered by the TabletGroupWatcher for the USER data level.", - MetricDocSection.MANAGER), + MetricDocSection.MANAGER, "User Tablet Watcher Errors", null, NUMBER), MANAGER_GOAL_STATE("accumulo.manager.goal.state", MetricType.GAUGE, "Manager goal state: -1=unknown, 0=CLEAN_STOP, 1=SAFE_MODE, 2=NORMAL.", - MetricDocSection.MANAGER), + MetricDocSection.MANAGER, "Manager Goal State", null, NUMBER), // Recovery Metrics RECOVERIES_IN_PROGRESS("accumulo.recoveries.in.progress", MetricType.GAUGE, - "The number of recoveries in progress.", MetricDocSection.GENERAL_SERVER), + "The number of recoveries in progress.", MetricDocSection.GENERAL_SERVER, + "Tablet Recoveries In Progress", null, NUMBER), RECOVERIES_LONGEST_RUNTIME("accumulo.recoveries.runtime.longest", MetricType.GAUGE, "The time (in milliseconds) of the longest running recovery.", - MetricDocSection.GENERAL_SERVER), + MetricDocSection.GENERAL_SERVER, "Tablet Recovery Longest Time", null, DURATION), RECOVERIES_AVG_PROGRESS("accumulo.recoveries.avg.progress", MetricType.GAUGE, "The average percentage (0.0 - 99.9) of the in progress recoveries.", - MetricDocSection.GENERAL_SERVER); + MetricDocSection.GENERAL_SERVER, "Tablet Recovery Avg Percent Complete", null, PERCENT); + + public static enum MonitorCssClass { + BYTES("big-size"), + DATE_END("end-date"), + DATE_START("start-date"), + DURATION("duration"), + IDLE_STATE("idle-state"), + MEMORY_STATE("memory-state"), + NUMBER("big-num"), + PERCENT("percent"); + + private final String cssClass; + + private MonitorCssClass(String cssClass) { + this.cssClass = cssClass; + } + + public String getCssClass() { + return this.cssClass; + } + } private final String name; private final MetricType type; private final String description; private final MetricDocSection section; + /* Name for HTML table header element in Monitor UI */ + private final String monitorColumnHeader; + /* Description for HTML table header element in Monitor UI, displayed on hover */ + private final String monitorColumnDescription; + /* CSS class for HTML table header element in Monitor UI */ + private final MonitorCssClass[] monitorColumnClasses; private static final Map NAME_TO_ENUM_MAP = new HashMap<>(); @@ -337,11 +429,15 @@ public enum Metric { } } - Metric(String name, MetricType type, String description, MetricDocSection section) { + Metric(String name, MetricType type, String description, MetricDocSection section, + String colHeader, String colDescription, MonitorCssClass... colClasses) { this.name = name; this.type = type; this.description = description; this.section = section; + this.monitorColumnHeader = colHeader; + this.monitorColumnDescription = colDescription; + this.monitorColumnClasses = colClasses; } public String getName() { @@ -360,6 +456,21 @@ public MetricDocSection getDocSection() { return section; } + public String getColumnHeader() { + return monitorColumnHeader; + } + + public MonitorCssClass[] getColumnClasses() { + return monitorColumnClasses; + } + + public String getColumnDescription() { + if (monitorColumnDescription == null) { + return description; + } + return monitorColumnDescription; + } + public enum MetricType { GAUGE, COUNTER, TIMER, LONG_TASK_TIMER, DISTRIBUTION_SUMMARY, FUNCTION_COUNTER, CACHE } @@ -409,4 +520,32 @@ public static Metric fromName(String name) { return metric; } + /** + * Returns a set of Metric names that should not be sent to the Monitor from + * {@code AbstractServer.getMetrics} + * + * @param serverType server type + * @return set of metrics not to send to Monitor + */ + public static Set getMonitorExclusions(ServerId.Type serverType) { + switch (serverType) { + case COMPACTOR: + return Set.of(MINC_PAUSED.getName(), RECOVERIES_AVG_PROGRESS.getName(), + RECOVERIES_IN_PROGRESS.getName(), RECOVERIES_LONGEST_RUNTIME.getName()); + case GARBAGE_COLLECTOR: + return Set.of(); + case MANAGER: + return Set.of(); + case MONITOR: + return Set.of(); + case SCAN_SERVER: + return Set.of(RECOVERIES_AVG_PROGRESS.getName(), RECOVERIES_IN_PROGRESS.getName(), + RECOVERIES_LONGEST_RUNTIME.getName()); + case TABLET_SERVER: + return Set.of(MAJC_PAUSED.getName()); + default: + throw new IllegalArgumentException("Unhandled server type: " + serverType); + } + } + } diff --git a/server/base/src/main/java/org/apache/accumulo/server/AbstractServer.java b/server/base/src/main/java/org/apache/accumulo/server/AbstractServer.java index 4adc0afe489..1cadeabaa33 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/AbstractServer.java +++ b/server/base/src/main/java/org/apache/accumulo/server/AbstractServer.java @@ -22,6 +22,7 @@ import java.net.UnknownHostException; import java.util.OptionalInt; +import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -39,6 +40,7 @@ import org.apache.accumulo.core.conf.SiteConfiguration; import org.apache.accumulo.core.data.ResourceGroupId; import org.apache.accumulo.core.lock.ServiceLock; +import org.apache.accumulo.core.metrics.Metric; import org.apache.accumulo.core.metrics.MetricsProducer; import org.apache.accumulo.core.process.thrift.MetricResponse; import org.apache.accumulo.core.process.thrift.MetricSource; @@ -95,6 +97,7 @@ public static void startServer(AbstractServer server, Logger LOG) throws Excepti private final AtomicBoolean shutdownRequested = new AtomicBoolean(false); private final AtomicBoolean shutdownComplete = new AtomicBoolean(false); private final AtomicBoolean closed = new AtomicBoolean(false); + private final Set monitorMetricExclusions; protected AbstractServer(ServerId.Type serverType, ServerOpts opts, BiFunction serverContextFactory, @@ -177,6 +180,7 @@ protected AbstractServer(ServerId.Type serverType, ServerOpts opts, default: throw new IllegalArgumentException("Unhandled server type: " + serverType); } + monitorMetricExclusions = Metric.getMonitorExclusions(serverType); } /** @@ -403,10 +407,12 @@ public MetricResponse getMetrics(TInfo tinfo, TCredentials credentials) throws T if (context.getMetricsInfo().isMetricsEnabled()) { Metrics.globalRegistry.getMeters().forEach(m -> { if (m.getId().getName().startsWith("accumulo.")) { - m.match(response::writeMeter, response::writeMeter, response::writeTimer, - response::writeDistributionSummary, response::writeLongTaskTimer, - response::writeMeter, response::writeMeter, response::writeFunctionTimer, - response::writeMeter); + if (!this.monitorMetricExclusions.contains(m.getId().getName())) { + m.match(response::writeMeter, response::writeMeter, response::writeTimer, + response::writeDistributionSummary, response::writeLongTaskTimer, + response::writeMeter, response::writeMeter, response::writeFunctionTimer, + response::writeMeter); + } } }); } diff --git a/server/monitor/pom.xml b/server/monitor/pom.xml index 089502e40c3..89a2597f9e8 100644 --- a/server/monitor/pom.xml +++ b/server/monitor/pom.xml @@ -54,6 +54,10 @@ com.github.ben-manes.caffeine caffeine + + com.google.code.gson + gson + com.google.guava guava @@ -242,6 +246,26 @@ + + org.codehaus.mojo + exec-maven-plugin + + + metrics-javascript + + java + + prepare-package + + org.apache.accumulo.monitor.next.views.ColumnJsGen + compile + + ${project.build.directory}/classes/org/apache/accumulo/monitor/resources/js/columns.js + + + + + org.apache.maven.plugins maven-resources-plugin diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/Endpoints.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/Endpoints.java index 9a809e9d3ca..e42e230183f 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/Endpoints.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/Endpoints.java @@ -33,6 +33,7 @@ import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.GET; +import jakarta.ws.rs.MatrixParam; import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; @@ -54,7 +55,7 @@ import org.apache.accumulo.monitor.next.deployment.DeploymentOverview; import org.apache.accumulo.monitor.next.ec.CompactorsSummary; import org.apache.accumulo.monitor.next.ec.CoordinatorSummary; -import org.apache.accumulo.monitor.next.sservers.ScanServerView; +import org.apache.accumulo.monitor.next.views.ServersView; import io.micrometer.core.instrument.Meter.Id; import io.micrometer.core.instrument.cumulative.CumulativeDistributionSummary; @@ -62,12 +63,17 @@ @Path("/") public class Endpoints { /** - * A {@code String} constant representing supplied resource group in path parameter. + * A {@code String} constant representing the supplied resource group in path parameter. */ private static final String GROUP_PARAM_KEY = "group"; /** - * A {@code String} constant representing supplied tableId in path parameter. + * A {@code String} constant representing the supplied server type in path parameter. + */ + private static final String SERVER_TYPE_KEY = "serverType"; + + /** + * A {@code String} constant representing the supplied tableId in path parameter. */ private static final String TABLEID_PARAM_KEY = "tableId"; @@ -271,11 +277,17 @@ public Map getScanServerAllMetricSummary() { } @GET - @Path("sservers/view") + @Path("servers/view") @Produces(MediaType.APPLICATION_JSON) - @Description("Returns a UI-ready view model for the Scan Server status page") - public ScanServerView getScanServerPageView() { - return monitor.getInformationFetcher().getSummaryForEndpoint().getScanServerView(); + @Description("Returns a UI-ready view model for server processes. Add ';serverType=' to URL") + public ServersView getServerProcessView(@MatrixParam(SERVER_TYPE_KEY) ServerId.Type serverType) { + ServersView view = + monitor.getInformationFetcher().getSummaryForEndpoint().getServerProcessView(serverType); + if (view == null) { + throw new NotFoundException( + "ServersView object for server type " + serverType.name() + " not found"); + } + return view; } @GET diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java index 1f5f9151147..9902e8c480b 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java @@ -211,7 +211,7 @@ public void newConnectionEvent() { } // Protect against NPE and wait for initial data gathering - public SystemInformation getSummary() throws InterruptedException { + private SystemInformation getSummary() throws InterruptedException { while (summaryRef.get() == null) { Thread.sleep(100); } @@ -254,7 +254,7 @@ public void onRemoval(@Nullable ServerId server, @Nullable MetricResponse respon @Override public void run() { - long refreshTime = 0; + long lastRunTime = 0; while (true) { @@ -263,7 +263,7 @@ public void run() { // If a connection has not been made in a while, stale data may be displayed. // Only refresh every 5s (old monitor logic). while (!newConnectionEvent.get() && connectionCount.get() == 0 - && NanoTime.millisElapsed(refreshTime, NanoTime.now()) > 5000) { + && NanoTime.millisElapsed(lastRunTime, NanoTime.now()) > 5000) { try { Thread.sleep(100); } catch (InterruptedException e) { @@ -275,7 +275,7 @@ public void run() { // reset the connection event flag newConnectionEvent.compareAndExchange(true, false); - LOG.info("Fetching metrics from servers"); + LOG.info("Fetching information from servers"); final List> futures = new ArrayList<>(); final SystemInformation summary = new SystemInformation(allMetrics, this.ctx); @@ -315,13 +315,18 @@ public void run() { } })); - long monitorFetchTimeout = + final long monitorFetchTimeout = ctx.getConfiguration().getTimeInMillis(Property.MONITOR_FETCH_TIMEOUT); - long allFuturesAdded = NanoTime.now(); + final long allFuturesAdded = NanoTime.now(); boolean tookToLong = false; while (!futures.isEmpty()) { if (NanoTime.millisElapsed(allFuturesAdded, NanoTime.now()) > monitorFetchTimeout) { + LOG.warn( + "Fetching information for Monitor has taken longer {}. Cancelling all" + + " remaining tasks and monitor will display old information. Resolve issue" + + " causing this or increase property {}.", + monitorFetchTimeout, Property.MONITOR_FETCH_TIMEOUT.getKey()); tookToLong = true; } @@ -344,26 +349,31 @@ public void run() { } } - summary.finish(); - - refreshTime = NanoTime.now(); - LOG.info("Finished fetching metrics from servers"); - LOG.info( - "All: {}, Manager: {}, Garbage Collector: {}, Compactors: {}, Scan Servers: {}, Tablet Servers: {}", - allMetrics.estimatedSize(), summary.getManager() != null, - summary.getGarbageCollector() != null, - summary.getCompactorAllMetricSummary().isEmpty() ? 0 - : summary.getCompactorAllMetricSummary().entrySet().iterator().next().getValue() - .count(), - summary.getSServerAllMetricSummary().isEmpty() ? 0 - : summary.getSServerAllMetricSummary().entrySet().iterator().next().getValue() - .count(), - summary.getTServerAllMetricSummary().isEmpty() ? 0 : summary.getTServerAllMetricSummary() - .entrySet().iterator().next().getValue().count()); - - SystemInformation oldSummary = summaryRef.getAndSet(summary); - if (oldSummary != null) { - oldSummary.clear(); + lastRunTime = NanoTime.now(); + + if (tookToLong) { + summary.clear(); + } else { + summary.finish(); + + LOG.info("Finished fetching metrics from servers"); + LOG.info( + "All: {}, Manager: {}, Garbage Collector: {}, Compactors: {}, Scan Servers: {}, Tablet Servers: {}", + allMetrics.estimatedSize(), summary.getManager() != null, + summary.getGarbageCollector() != null, + summary.getCompactorAllMetricSummary().isEmpty() ? 0 + : summary.getCompactorAllMetricSummary().entrySet().iterator().next().getValue() + .count(), + summary.getSServerAllMetricSummary().isEmpty() ? 0 + : summary.getSServerAllMetricSummary().entrySet().iterator().next().getValue() + .count(), + summary.getTServerAllMetricSummary().isEmpty() ? 0 : summary + .getTServerAllMetricSummary().entrySet().iterator().next().getValue().count()); + + SystemInformation oldSummary = summaryRef.getAndSet(summary); + if (oldSummary != null) { + oldSummary.clear(); + } } } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java index 3949e5766b0..e8e2132d377 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java @@ -18,11 +18,14 @@ */ package org.apache.accumulo.monitor.next; +import static com.google.common.base.Suppliers.memoize; + import java.nio.ByteBuffer; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.EnumMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -36,6 +39,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.LongAdder; +import java.util.function.Supplier; import java.util.stream.Stream; import org.apache.accumulo.core.Constants; @@ -59,7 +63,7 @@ import org.apache.accumulo.core.spi.balancer.TableLoadBalancer; import org.apache.accumulo.core.util.compaction.RunningCompactionInfo; import org.apache.accumulo.monitor.next.deployment.DeploymentOverview; -import org.apache.accumulo.monitor.next.sservers.ScanServerView; +import org.apache.accumulo.monitor.next.views.ServersView; import org.apache.accumulo.server.ServerContext; import org.apache.accumulo.server.conf.TableConfiguration; import org.apache.accumulo.server.metrics.MetricResponseWrapper; @@ -410,9 +414,9 @@ public Stream stream() { private final Set configuredCompactionResourceGroups = ConcurrentHashMap.newKeySet(); - private long timestamp = 0; - private ScanServerView scanServerView = new ScanServerView(0L, List.of(), - new ScanServerView.Status(false, false, false, 0, 0, 0L, "OK", null)); + private final AtomicLong timestamp = new AtomicLong(0); + private EnumMap> serverMetricsView = + new EnumMap<>(ServerId.Type.class); private DeploymentOverview deploymentOverview = new DeploymentOverview(0L, List.of()); private final int rgLongRunningCompactionSize; @@ -447,6 +451,7 @@ public void clear() { runningCompactionsPerGroup.clear(); runningCompactionsPerTable.clear(); configuredCompactionResourceGroups.clear(); + serverMetricsView.clear(); } private void updateAggregates(final MetricResponse response, @@ -582,6 +587,7 @@ public void processError(ServerId server) { public void processMetricsError(ServerId server) { problemHosts.add(server); metricProblemHosts.add(server); + allMetrics.invalidate(server); } public void addConfiguredCompactionGroups(Set groups) { @@ -648,15 +654,44 @@ public void finish() { } } - Set scanServers = new HashSet<>(); - sservers.values().forEach(scanServers::addAll); - int problemScanServerCount = (int) problemHosts.stream() - .filter(serverId -> serverId.getType() == ServerId.Type.SCAN_SERVER).count(); - var responses = allMetrics.getAllPresent(scanServers).values(); - timestamp = System.currentTimeMillis(); + timestamp.set(System.currentTimeMillis()); + + for (final ServerId.Type type : ServerId.Type.values()) { + long problemHostCount = + problemHosts.stream().filter(serverId -> serverId.getType() == type).count(); + Set servers = new HashSet<>(); + switch (type) { + case COMPACTOR: + compactors.values().forEach(servers::addAll); + serverMetricsView.put(type, memoize( + () -> new ServersView(servers, problemHostCount, allMetrics, timestamp.get()))); + break; + case GARBAGE_COLLECTOR: + servers.add(gc.get()); + serverMetricsView.put(type, memoize( + () -> new ServersView(servers, problemHostCount, allMetrics, timestamp.get()))); + break; + case MANAGER: + servers.add(manager.get()); + serverMetricsView.put(type, memoize( + () -> new ServersView(servers, problemHostCount, allMetrics, timestamp.get()))); + break; + case SCAN_SERVER: + sservers.values().forEach(servers::addAll); + serverMetricsView.put(type, memoize( + () -> new ServersView(servers, problemHostCount, allMetrics, timestamp.get()))); + break; + case TABLET_SERVER: + tservers.values().forEach(servers::addAll); + serverMetricsView.put(type, memoize( + () -> new ServersView(servers, problemHostCount, allMetrics, timestamp.get()))); + break; + case MONITOR: + default: + break; + } + } deploymentOverview = DeploymentOverview.fromSummary(deployment, timestamp); - scanServerView = ScanServerView.fromMetrics(responses, scanServers.size(), - problemScanServerCount, timestamp); } public Set getResourceGroups() { @@ -747,11 +782,15 @@ public Set getSuggestions() { } public long getTimestamp() { - return this.timestamp; + return this.timestamp.get(); } - public ScanServerView getScanServerView() { - return this.scanServerView; + public ServersView getServerProcessView(ServerId.Type type) { + Supplier view = this.serverMetricsView.get(type); + if (view != null) { + return view.get(); + } + return null; } public static Number getMetricValue(FMetric metric) { diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/deployment/DeploymentOverview.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/deployment/DeploymentOverview.java index 536399fda16..50375e27bbe 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/deployment/DeploymentOverview.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/deployment/DeploymentOverview.java @@ -21,6 +21,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import org.apache.accumulo.core.client.admin.servers.ServerId; import org.apache.accumulo.core.data.ResourceGroupId; @@ -40,9 +41,9 @@ public record DeploymentRow(String resourceGroup, String serverType, long total, } public static DeploymentOverview fromSummary( - Map> deployment, long lastUpdate) { + Map> deployment, AtomicLong lastUpdate) { if (deployment == null || deployment.isEmpty()) { - return new DeploymentOverview(lastUpdate, List.of()); + return new DeploymentOverview(lastUpdate.get(), List.of()); } var breakdown = @@ -63,7 +64,7 @@ public static DeploymentOverview fromSummary( }); }).toList(); - return new DeploymentOverview(lastUpdate, breakdown); + return new DeploymentOverview(lastUpdate.get(), breakdown); } /** diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/sservers/ScanServerView.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/sservers/ScanServerView.java deleted file mode 100644 index 05ee018d82f..00000000000 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/sservers/ScanServerView.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.accumulo.monitor.next.sservers; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Stream; - -import org.apache.accumulo.core.metrics.Metric; -import org.apache.accumulo.core.metrics.flatbuffers.FMetric; -import org.apache.accumulo.core.metrics.flatbuffers.FTag; -import org.apache.accumulo.core.process.thrift.MetricResponse; -import org.apache.accumulo.monitor.next.SystemInformation; -import org.apache.accumulo.server.metrics.MetricResponseWrapper; - -/** - * Data Transfer Object (DTO) for the Monitor Scan Servers page. It transforms backend metrics into - * a UI-ready JSON response consumed by the frontend; each record component is serialized as a JSON - * field. - */ -public record ScanServerView(long lastUpdate, List servers, Status status) { - - /** - * all the data needed for a row in the table in monitor - */ - public record Row(String host, String resourceGroup, long lastContact, boolean metricsAvailable, - Number openFiles, Number queries, Number scannedEntries, Number queryResults, - Number queryResultBytes, Number busyTimeouts, Number reservationConflicts, - Number zombieThreads, Number serverIdle, Number lowMemoryDetected, - Number scansPausedForMemory, Number scansReturnedEarlyForMemory) { - } - - /** - * all the data needed for the ScanServer status indicator(s) - */ - public record Status(boolean hasScanServers, boolean hasProblemScanServers, - boolean hasMissingMetrics, int scanServerCount, int problemScanServerCount, - long missingMetricServerCount, String level, String message) { - } - - private static final String LEVEL_OK = "OK"; - private static final String LEVEL_WARN = "WARN"; - - public static ScanServerView fromMetrics(Collection responses, - int scanServerCount, int problemScanServerCount, long snapshotTime) { - var rows = rows(responses, snapshotTime); - Status status = buildStatus(scanServerCount, problemScanServerCount, rows); - return new ScanServerView(snapshotTime, rows, status); - } - - private static List rows(Collection responses, long nowMs) { - if (responses == null) { - return List.of(); - } - return responses.stream().map(response -> toRow(response, nowMs)) - .sorted(Comparator.comparing(Row::resourceGroup).thenComparing(Row::host)).toList(); - } - - private static Status buildStatus(int scanServerCount, int problemScanServerCount, - List rows) { - long missingMetricServerCount = rows.stream().filter(row -> !row.metricsAvailable()).count(); - boolean hasScanServers = scanServerCount > 0; - boolean hasProblemScanServers = problemScanServerCount > 0; - boolean hasMissingMetrics = missingMetricServerCount > 0; - - List warnings = new ArrayList<>(2); - if (hasProblemScanServers) { - warnings.add("one or more scan servers are unavailable"); - } - if (hasMissingMetrics) { - warnings.add("ScanServer metrics are not present (are metrics enabled?)"); - } - - if (warnings.isEmpty()) { - // no warnings, set status to OK - return new Status(hasScanServers, false, false, scanServerCount, 0, 0, LEVEL_OK, null); - } - - final String message = "WARN: " + String.join("; ", warnings) + "."; - return new Status(hasScanServers, hasProblemScanServers, hasMissingMetrics, scanServerCount, - problemScanServerCount, missingMetricServerCount, LEVEL_WARN, message); - } - - private static Row toRow(MetricResponse response, long nowMs) { - if (response == null) { - return new Row(null, null, 0, false, null, null, null, null, null, null, null, null, null, - null, null, null); - } - - var values = metricValuesByName(response); - var openFiles = values.get(Metric.SCAN_OPEN_FILES.getName()); - var queries = values.get(Metric.SCAN_QUERIES.getName()); - var scannedEntries = values.get(Metric.SCAN_SCANNED_ENTRIES.getName()); - var queryResults = values.get(Metric.SCAN_QUERY_SCAN_RESULTS.getName()); - var queryResultBytes = values.get(Metric.SCAN_QUERY_SCAN_RESULTS_BYTES.getName()); - var busyTimeouts = values.get(Metric.SCAN_BUSY_TIMEOUT_COUNT.getName()); - var reservationConflicts = values.get(Metric.SCAN_RESERVATION_CONFLICT_COUNTER.getName()); - var zombieThreads = values.get(Metric.SCAN_ZOMBIE_THREADS.getName()); - var serverIdle = values.get(Metric.SERVER_IDLE.getName()); - var lowMemoryDetected = values.get(Metric.LOW_MEMORY.getName()); - var scansPausedForMemory = values.get(Metric.SCAN_PAUSED_FOR_MEM.getName()); - var scansReturnedEarlyForMemory = values.get(Metric.SCAN_RETURN_FOR_MEM.getName()); - - boolean allMetricsPresent = - Stream.of(openFiles, queries, scannedEntries, queryResults, queryResultBytes, busyTimeouts, - reservationConflicts, zombieThreads, serverIdle, lowMemoryDetected, - scansPausedForMemory, scansReturnedEarlyForMemory).allMatch(Objects::nonNull); - - long lastContact = Math.max(0, nowMs - response.getTimestamp()); - - return new Row(response.getServer(), response.getResourceGroup(), lastContact, - allMetricsPresent, openFiles, queries, scannedEntries, queryResults, queryResultBytes, - busyTimeouts, reservationConflicts, zombieThreads, serverIdle, lowMemoryDetected, - scansPausedForMemory, scansReturnedEarlyForMemory); - } - - private static Map metricValuesByName(MetricResponse response) { - var values = new HashMap(); - if (response == null || response.getMetrics() == null || response.getMetrics().isEmpty()) { - return values; - } - - for (var binary : response.getMetrics()) { - var metric = FMetric.getRootAsFMetric(binary); - var metricStatistic = extractStatistic(metric); - if (metricStatistic == null || metricStatistic.equals("value") - || metricStatistic.equals("count")) { - values.putIfAbsent(metric.name(), SystemInformation.getMetricValue(metric)); - } - } - return values; - } - - private static String extractStatistic(FMetric metric) { - for (int i = 0; i < metric.tagsLength(); i++) { - FTag tag = metric.tags(i); - if (MetricResponseWrapper.STATISTIC_TAG.equals(tag.key())) { - return normalizeStatistic(tag.value()); - } - } - return null; - } - - private static String normalizeStatistic(String statistic) { - if (statistic == null) { - return null; - } - return statistic.toLowerCase(); - } - -} diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ColumnJsGen.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ColumnJsGen.java new file mode 100644 index 00000000000..b2593dc6b48 --- /dev/null +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ColumnJsGen.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.monitor.next.views; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.accumulo.core.util.LazySingletons.GSON; +import static org.apache.accumulo.monitor.next.views.ServersView.ADDR_COL_NAME; +import static org.apache.accumulo.monitor.next.views.ServersView.RG_COL_NAME; +import static org.apache.accumulo.monitor.next.views.ServersView.TIME_COL_NAME; +import static org.apache.accumulo.monitor.next.views.ServersView.TYPE_COL_NAME; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.apache.accumulo.core.metrics.Metric; +import org.apache.accumulo.core.metrics.Metric.MonitorCssClass; + +/** + * This class generates a map of metric name to column information for the Monitor + */ +public class ColumnJsGen { + + public record ColumnInformation(String header, String description, String classes) { + }; + + private static void printHeader(PrintStream out) { + final String hdr = """ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + "use strict"; + + const COLUMN_MAP = new Map(["""; + out.println(hdr); + } + + private static void printMetrics(PrintStream out) { + + // Put in tree map to sort metrics by name in the output + Map output = new TreeMap<>(); + for (Metric m : Metric.values()) { + MonitorCssClass[] classes = m.getColumnClasses(); + String css = + Arrays.stream(classes).map(c -> c.getCssClass()).collect(Collectors.joining(" ")); + output.put(m.getName(), + new ColumnInformation(m.getColumnHeader(), m.getColumnDescription(), css)); + } + + // Add non-metric columns that are part of the ServersView + // object that is returned from the Monitor endpoint + output.put(ADDR_COL_NAME, new ColumnInformation(ADDR_COL_NAME, "Server Address", "firstcell")); + output.put(RG_COL_NAME, + new ColumnInformation(RG_COL_NAME, "Resource Group Name", "resource-group")); + output.put(TIME_COL_NAME, + new ColumnInformation(TIME_COL_NAME, "Last Contact Time", "duration")); + output.put(TYPE_COL_NAME, + new ColumnInformation(TYPE_COL_NAME, "Server Process Type", "server-type")); + + final Set keys = output.keySet(); + final int numKeys = keys.size(); + int counter = 0; + for (String key : keys) { + counter++; + out.println(" [\"%s\", %s]%s".formatted(key, GSON.get().toJson(output.get(key)), + counter == numKeys ? "" : ",")); + + } + } + + private static void printFooter(PrintStream out) { + final String footer = """ + ]); + """; + out.println(footer); + } + + public static void main(String args[]) throws IOException { + if (args.length != 1) { + throw new IllegalArgumentException( + "Usage: " + ColumnJsGen.class.getName() + " "); + } + try (var printStream = new PrintStream(args[0], UTF_8)) { + printHeader(printStream); + printMetrics(printStream); + printFooter(printStream); + } + } + +} diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ServersView.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ServersView.java new file mode 100644 index 00000000000..6b476c4ad3f --- /dev/null +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ServersView.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.monitor.next.views; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.accumulo.core.client.admin.servers.ServerId; +import org.apache.accumulo.core.metrics.flatbuffers.FMetric; +import org.apache.accumulo.core.metrics.flatbuffers.FTag; +import org.apache.accumulo.core.process.thrift.MetricResponse; +import org.apache.accumulo.monitor.next.SystemInformation; +import org.apache.accumulo.server.metrics.MetricResponseWrapper; + +import com.github.benmanes.caffeine.cache.Cache; + +/** + * Generic Data Transfer Object (DTO) for a set of Accumulo server processes of the same type. The + * response object contains several fields: + * + * + *
+ * columns - contains an array of column definitions that can be used to create the table headers
+ *           and Data Table columns
+ * data    - an array of objects that can be used for the Data Table data definition
+ * status  - overall status information, counts, warnings, etc.
+ * 
+ */ +public class ServersView { + + /** + * all the data needed for the Server status indicator(s) + */ + public record Status(boolean hasServers, boolean hasProblemServers, boolean hasMissingMetrics, + long serverCount, long problemServerCount, long missingMetricServerCount, String level, + String message) { + } + + public static final String TYPE_COL_NAME = "Server Type"; + public static final String RG_COL_NAME = "Resource Group"; + public static final String ADDR_COL_NAME = "Server Address"; + public static final String TIME_COL_NAME = "Last Contact"; + + private static final String LEVEL_OK = "OK"; + private static final String LEVEL_WARN = "WARN"; + + public final List> data = new ArrayList<>(); + public final Set columns = new TreeSet<>(); + public final Status status; + public final long timestamp; + + public ServersView(final Set servers, final long problemServerCount, + final Cache allMetrics, final long timestamp) { + + AtomicInteger serversMissingMetrics = new AtomicInteger(0); + servers.forEach(sid -> { + Map metrics = new TreeMap<>(); + + columns.add(TYPE_COL_NAME); + metrics.put(TYPE_COL_NAME, sid.getType().name()); + columns.add(RG_COL_NAME); + metrics.put(RG_COL_NAME, sid.getResourceGroup().canonical()); + columns.add(ADDR_COL_NAME); + metrics.put(ADDR_COL_NAME, sid.toHostPortString()); + + MetricResponse mr = allMetrics.getIfPresent(sid); + if (mr != null) { + // Don't use the timestamp for the last contact duration, + // use the current time. + columns.add(TIME_COL_NAME); + metrics.put(TIME_COL_NAME, System.currentTimeMillis() - mr.getTimestamp()); + + Map serverMetrics = metricValuesByName(mr); + for (Entry e : serverMetrics.entrySet()) { + columns.add(e.getKey()); + metrics.put(e.getKey(), e.getValue()); + } + data.add(metrics); + } else { + serversMissingMetrics.incrementAndGet(); + } + }); + status = buildStatus(servers.size(), problemServerCount, serversMissingMetrics.get()); + this.timestamp = timestamp; + } + + private static Status buildStatus(int serverCount, long problemServerCount, + int serversMissingMetrics) { + final boolean hasServers = serverCount > 0; + final boolean hasProblemServers = problemServerCount > 0; + final boolean hasMissingMetrics = serversMissingMetrics > 0; + + List warnings = new ArrayList<>(2); + if (hasProblemServers) { + warnings.add("One or more servers are unavailable"); + } + if (hasMissingMetrics) { + warnings.add("Metrics are not present (are metrics enabled?)"); + } + + if (warnings.isEmpty()) { + // no warnings, set status to OK + return new Status(hasServers, false, false, serverCount, 0, 0, LEVEL_OK, null); + } + + final String message = "WARN: " + String.join("; ", warnings) + "."; + return new Status(hasServers, hasProblemServers, hasMissingMetrics, serverCount, + problemServerCount, serversMissingMetrics, LEVEL_WARN, message); + } + + public static Map metricValuesByName(MetricResponse response) { + var values = new HashMap(); + if (response == null || response.getMetrics() == null || response.getMetrics().isEmpty()) { + return values; + } + + for (var binary : response.getMetrics()) { + var metric = FMetric.getRootAsFMetric(binary); + var metricStatistic = extractStatistic(metric); + if (metricStatistic == null || metricStatistic.equals("value") + || metricStatistic.equals("count")) { + // For metrics with the same name, but different tags, compute a sum + Number val = SystemInformation.getMetricValue(metric); + values.compute(metric.name(), (k, v) -> { + if (v == null) { + return val; + } else { + if (v instanceof Integer i) { + return i + val.intValue(); + } else if (v instanceof Long l) { + return l + val.longValue(); + } else if (v instanceof Double d) { + return d + val.doubleValue(); + } else { + throw new RuntimeException("Unexpected value type: " + val.getClass()); + } + } + }); + values.putIfAbsent(metric.name(), SystemInformation.getMetricValue(metric)); + } + } + return values; + } + + private static String extractStatistic(FMetric metric) { + for (int i = 0; i < metric.tagsLength(); i++) { + FTag tag = metric.tags(i); + if (MetricResponseWrapper.STATISTIC_TAG.equals(tag.key())) { + return normalizeStatistic(tag.value()); + } + } + return null; + } + + private static String normalizeStatistic(String statistic) { + if (statistic == null) { + return null; + } + return statistic.toLowerCase(); + } + +} diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/css/colReorder.bootstrap5.css b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/css/colReorder.bootstrap5.css new file mode 100644 index 00000000000..fc2b916033c --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/css/colReorder.bootstrap5.css @@ -0,0 +1,19 @@ +table.DTCR_clonedTable.dataTable { + position: absolute !important; + background-color: rgba(255, 255, 255, 0.7); + z-index: 202; + border-radius: 4px; +} + +div.DTCR_pointer { + width: 1px; + background-color: #0d6efd; + z-index: 201; +} + +html.dark table.DTCR_clonedTable.dataTable { + background-color: rgba(33, 33, 33, 0.9); +} +html.dark div.DTCR_pointer { + background-color: rgb(13, 110, 253); +} diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/colReorder.bootstrap5.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/colReorder.bootstrap5.js new file mode 100644 index 00000000000..149f7d2f7a6 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/colReorder.bootstrap5.js @@ -0,0 +1,58 @@ +/*! Bootstrap 5 styling wrapper for ColReorder + * © SpryMedia Ltd - datatables.net/license + */ + +(function( factory ){ + if ( typeof define === 'function' && define.amd ) { + // AMD + define( ['jquery', 'datatables.net-bs5', 'datatables.net-colreorder'], function ( $ ) { + return factory( $, window, document ); + } ); + } + else if ( typeof exports === 'object' ) { + // CommonJS + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net-bs5')(root, $); + } + + if ( ! $.fn.dataTable.ColReorder ) { + require('datatables.net-colreorder')(root, $); + } + }; + + if (typeof window === 'undefined') { + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } + } + else { + // Browser + factory( jQuery, window, document ); + } +}(function( $, window, document, undefined ) { +'use strict'; +var DataTable = $.fn.dataTable; + + + + +return DataTable; +})); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/dataTables.colReorder.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/dataTables.colReorder.js new file mode 100644 index 00000000000..0158b970e1f --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/dataTables.colReorder.js @@ -0,0 +1,1518 @@ +/*! ColReorder 1.7.0 + * © SpryMedia Ltd - datatables.net/license + */ + +(function( factory ){ + if ( typeof define === 'function' && define.amd ) { + // AMD + define( ['jquery', 'datatables.net'], function ( $ ) { + return factory( $, window, document ); + } ); + } + else if ( typeof exports === 'object' ) { + // CommonJS + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net')(root, $); + } + }; + + if (typeof window === 'undefined') { + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } + } + else { + // Browser + factory( jQuery, window, document ); + } +}(function( $, window, document, undefined ) { +'use strict'; +var DataTable = $.fn.dataTable; + + + +/** + * @summary ColReorder + * @description Provide the ability to reorder columns in a DataTable + * @version 1.7.0 + * @author SpryMedia Ltd + * @contact datatables.net + * @copyright SpryMedia Ltd. + * + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license/mit + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ + +/** + * Switch the key value pairing of an index array to be value key (i.e. the old value is now the + * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ]. + * @method fnInvertKeyValues + * @param array aIn Array to switch around + * @returns array + */ +function fnInvertKeyValues( aIn ) +{ + var aRet=[]; + for ( var i=0, iLen=aIn.length ; i= iCols ) + { + this.oApi._fnLog( oSettings, 1, "ColReorder 'from' index is out of bounds: "+iFrom ); + return; + } + + if ( iTo < 0 || iTo >= iCols ) + { + this.oApi._fnLog( oSettings, 1, "ColReorder 'to' index is out of bounds: "+iTo ); + return; + } + + /* + * Calculate the new column array index, so we have a mapping between the old and new + */ + var aiMapping = []; + for ( i=0, iLen=iCols ; i this.s.fixed-1 && i < iLen - this.s.fixedRight ) + { + this._fnMouseListener( i, this.s.dt.aoColumns[i].nTh ); + } + + /* Mark the original column order for later reference */ + this.s.dt.aoColumns[i]._ColReorder_iOrigCol = i; + } + + /* State saving */ + this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) { + that._fnStateSave.call( that, oData ); + }, "ColReorder_State" ); + + this.s.dt.oApi._fnCallbackReg(this.s.dt, 'aoStateLoadParams', function(oS, oData) { + that.s.dt._colReorder.fnOrder(oData.ColReorder, true); + }) + + /* An initial column order has been specified */ + var aiOrder = null; + if ( this.s.init.aiOrder ) + { + aiOrder = this.s.init.aiOrder.slice(); + } + + /* State loading, overrides the column order given */ + if ( this.s.dt.oLoadedState && typeof this.s.dt.oLoadedState.ColReorder != 'undefined' && + this.s.dt.oLoadedState.ColReorder.length == this.s.dt.aoColumns.length ) + { + aiOrder = this.s.dt.oLoadedState.ColReorder; + } + + /* If we have an order to apply - do so */ + if ( aiOrder ) + { + /* We might be called during or after the DataTables initialisation. If before, then we need + * to wait until the draw is done, if after, then do what we need to do right away + */ + if ( !that.s.dt._bInitComplete ) + { + var bDone = false; + $(table).on( 'draw.dt.colReorder', function () { + if ( !that.s.dt._bInitComplete && !bDone ) + { + bDone = true; + var resort = fnInvertKeyValues( aiOrder ); + that._fnOrderColumns.call( that, resort ); + } + } ); + } + else + { + var resort = fnInvertKeyValues( aiOrder ); + that._fnOrderColumns.call( that, resort ); + } + } + else { + this._fnSetColumnIndexes(); + } + + // Destroy clean up + $(table).on( 'destroy.dt.colReorder', function () { + // Restore table to original order from when it was loaded + that.fnReset(); + + $(table).off( 'destroy.dt.colReorder draw.dt.colReorder' ); + + $.each( that.s.dt.aoColumns, function (i, column) { + $(column.nTh).off('.ColReorder'); + $(column.nTh).removeAttr('data-column-index'); + } ); + + that.s.dt._colReorder = null; + that.s = null; + } ); + }, + + + /** + * Set the column order from an array + * @method _fnOrderColumns + * @param array a An array of integers which dictate the column order that should be applied + * @returns void + * @private + */ + "_fnOrderColumns": function ( a ) + { + var changed = false; + + if ( a.length != this.s.dt.aoColumns.length ) + { + this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "ColReorder - array reorder does not "+ + "match known number of columns. Skipping." ); + return; + } + + for ( var i=0, iLen=a.length ; i= 0) { + i--; + + if (i <= 0) { + return null; + } + + if (that.s.aoTargets[i+1].x !== that.s.aoTargets[i].x) { + return that.s.aoTargets[i]; + } + } + }; + var firstNotHidden = function () { + for (var i=0 ; i0 ; i--) { + if (that.s.aoTargets[i].x !== that.s.aoTargets[i-1].x) { + return that.s.aoTargets[i]; + } + } + }; + + for (var i = 1; i < this.s.aoTargets.length; i++) { + var prevTarget = targetsPrev(i); + if (! prevTarget) { + prevTarget = firstNotHidden(); + } + + var prevTargetMiddle = prevTarget.x + (this.s.aoTargets[i].x - prevTarget.x) / 2; + + if (this._fnIsLtr()) { + if (cursorXPosiotion < prevTargetMiddle ) { + target = prevTarget; + break; + } + } + else { + if (cursorXPosiotion > prevTargetMiddle) { + target = prevTarget; + break; + } + } + } + + if (target) { + this.dom.pointer.css('left', target.x); + this.s.mouse.toIndex = target.to; + } + else { + // The insert element wasn't positioned in the array (less than + // operator), so we put it at the end + this.dom.pointer.css( 'left', lastNotHidden().x ); + this.s.mouse.toIndex = lastNotHidden().to; + } + + // Perform reordering if realtime updating is on and the column has moved + if ( this.s.init.bRealtime && lastToIndex !== this.s.mouse.toIndex ) { + this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex ); + this.s.mouse.fromIndex = this.s.mouse.toIndex; + + // Not great for performance, but required to keep everything in alignment + if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" ) + { + this.s.dt.oInstance.fnAdjustColumnSizing( false ); + } + + this._fnRegions(); + } + }, + + + /** + * Finish off the mouse drag and insert the column where needed + * @method _fnMouseUp + * @param event e Mouse event + * @returns void + * @private + */ + "_fnMouseUp": function ( e ) + { + var that = this; + + $(document).off( '.ColReorder' ); + + if ( this.dom.drag !== null ) + { + /* Remove the guide elements */ + this.dom.drag.remove(); + this.dom.pointer.remove(); + this.dom.drag = null; + this.dom.pointer = null; + + /* Actually do the reorder */ + this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex, true ); + this._fnSetColumnIndexes(); + + /* When scrolling we need to recalculate the column sizes to allow for the shift */ + if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" ) + { + this.s.dt.oInstance.fnAdjustColumnSizing( false ); + } + + /* Save the state */ + this.s.dt.oInstance.oApi._fnSaveState( this.s.dt ); + + if ( this.s.reorderCallback !== null ) + { + this.s.reorderCallback.call( this ); + } + } + }, + + + /** + * Calculate a cached array with the points of the column inserts, and the + * 'to' points + * @method _fnRegions + * @returns void + * @private + */ + "_fnRegions": function () + { + var aoColumns = this.s.dt.aoColumns; + var isLTR = this._fnIsLtr(); + this.s.aoTargets.splice(0, this.s.aoTargets.length); + var lastBound = $(this.s.dt.nTable).offset().left; + + var aoColumnBounds = []; + $.each(aoColumns, function (i, column) { + if (column.bVisible && column.nTh.style.display !== 'none') { + var nth = $(column.nTh); + var bound = nth.offset().left; + + if (isLTR) { + bound += nth.outerWidth(); + } + + aoColumnBounds.push({ + index: i, + bound: bound + }); + + lastBound = bound; + } + else { + aoColumnBounds.push({ + index: i, + bound: lastBound + }); + } + }); + + var firstColumn = aoColumnBounds[0]; + var firstColumnWidth = $(aoColumns[firstColumn.index].nTh).outerWidth(); + + this.s.aoTargets.push({ + to: 0, + x: firstColumn.bound - firstColumnWidth + }); + + for (var i = 0; i < aoColumnBounds.length; i++) { + var columnBound = aoColumnBounds[i]; + var iToPoint = columnBound.index; + + /* For the column / header in question, we want it's position to remain the same if the + * position is just to it's immediate left or right, so we only increment the counter for + * other columns + */ + if (columnBound.index < this.s.mouse.fromIndex) { + iToPoint++; + } + + this.s.aoTargets.push({ + to: iToPoint, + x: columnBound.bound + }); + } + + /* Disallow columns for being reordered by drag and drop, counting right to left */ + if ( this.s.fixedRight !== 0 ) + { + this.s.aoTargets.splice( this.s.aoTargets.length - this.s.fixedRight ); + } + + /* Disallow columns for being reordered by drag and drop, counting left to right */ + if ( this.s.fixed !== 0 ) + { + this.s.aoTargets.splice( 0, this.s.fixed ); + } + }, + + + /** + * Copy the TH element that is being drags so the user has the idea that they are actually + * moving it around the page. + * @method _fnCreateDragNode + * @returns void + * @private + */ + "_fnCreateDragNode": function () + { + var scrolling = this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== ""; + + var origCell = this.s.dt.aoColumns[ this.s.mouse.targetIndex ].nTh; + var origTr = origCell.parentNode; + var origThead = origTr.parentNode; + var origTable = origThead.parentNode; + var cloneCell = $(origCell).clone(); + + // This is a slightly odd combination of jQuery and DOM, but it is the + // fastest and least resource intensive way I could think of cloning + // the table with just a single header cell in it. + this.dom.drag = $(origTable.cloneNode(false)) + .addClass( 'DTCR_clonedTable' ) + .append( + $(origThead.cloneNode(false)).append( + $(origTr.cloneNode(false)).append( + cloneCell[0] + ) + ) + ) + .css( { + position: 'absolute', + top: 0, + left: 0, + width: $(origCell).outerWidth(), + height: $(origCell).outerHeight() + } ) + .appendTo( 'body' ); + + this.dom.pointer = $('
') + .addClass( 'DTCR_pointer' ) + .css( { + position: 'absolute', + top: scrolling ? + $($(this.s.dt.nScrollBody).parent()).offset().top : + $(this.s.dt.nTable).offset().top, + height : scrolling ? + $($(this.s.dt.nScrollBody).parent()).height() : + $(this.s.dt.nTable).height() + } ) + .appendTo( 'body' ); + }, + + + /** + * Add a data attribute to the column headers, so we know the index of + * the row to be reordered. This allows fast detection of the index, and + * for this plug-in to work with FixedHeader which clones the nodes. + * @private + */ + "_fnSetColumnIndexes": function () + { + $.each( this.s.dt.aoColumns, function (i, column) { + $(column.nTh).attr('data-column-index', i); + } ); + }, + + + /** + * Get cursor position regardless of mouse or touch input + * @param {Event} e jQuery Event + * @param {string} prop Property to get + * @return {number} Value + */ + _fnCursorPosition: function ( e, prop ) { + if ( e.type.indexOf('touch') !== -1 ) { + return e.originalEvent.touches[0][ prop ]; + } + return e[ prop ]; + }, + + _fnIsLtr: function () { + return $(this.s.dt.nTable).css('direction') !== "rtl"; + } +} ); + + + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Static parameters + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +/** + * ColReorder default settings for initialisation + * @namespace + * @static + */ +ColReorder.defaults = { + /** + * Predefined ordering for the columns that will be applied automatically + * on initialisation. If not specified then the order that the columns are + * found to be in the HTML is the order used. + * @type array + * @default null + * @static + */ + aiOrder: null, + + /** + * ColReorder enable on initialisation + * @type boolean + * @default true + * @static + */ + bEnable: true, + + /** + * Redraw the table's column ordering as the end user draws the column + * (`true`) or wait until the mouse is released (`false` - default). Note + * that this will perform a redraw on each reordering, which involves an + * Ajax request each time if you are using server-side processing in + * DataTables. + * @type boolean + * @default false + * @static + */ + bRealtime: true, + + /** + * Indicate how many columns should be fixed in position (counting from the + * left). This will typically be 1 if used, but can be as high as you like. + * @type int + * @default 0 + * @static + */ + iFixedColumnsLeft: 0, + + /** + * As `iFixedColumnsRight` but counting from the right. + * @type int + * @default 0 + * @static + */ + iFixedColumnsRight: 0, + + /** + * Callback function that is fired when columns are reordered. The `column- + * reorder` event is preferred over this callback + * @type function():void + * @default null + * @static + */ + fnReorderCallback: null +}; + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Constants + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** + * ColReorder version + * @constant version + * @type String + * @default As code + */ +ColReorder.version = "1.7.0"; + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables interfaces + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// Expose +$.fn.dataTable.ColReorder = ColReorder; +$.fn.DataTable.ColReorder = ColReorder; + + +// Register a new feature with DataTables +if ( typeof $.fn.dataTable == "function" && + typeof $.fn.dataTableExt.fnVersionCheck == "function" && + $.fn.dataTableExt.fnVersionCheck('1.10.8') ) +{ + $.fn.dataTableExt.aoFeatures.push( { + "fnInit": function( settings ) { + var table = settings.oInstance; + + if ( ! settings._colReorder ) { + var dtInit = settings.oInit; + var opts = dtInit.colReorder || dtInit.oColReorder || {}; + + new ColReorder( settings, opts ); + } + else { + table.oApi._fnLog( settings, 1, "ColReorder attempted to initialise twice. Ignoring second" ); + } + + return null; /* No node for DataTables to insert */ + }, + "cFeature": "R", + "sFeature": "ColReorder" + } ); +} +else { + alert( "Warning: ColReorder requires DataTables 1.10.8 or greater - www.datatables.net/download"); +} + + +// Attach a listener to the document which listens for DataTables initialisation +// events so we can automatically initialise +$(document).on( 'preInit.dt.colReorder', function (e, settings) { + if ( e.namespace !== 'dt' ) { + return; + } + + var init = settings.oInit.colReorder; + var defaults = DataTable.defaults.colReorder; + + if ( init || defaults ) { + var opts = $.extend( {}, init, defaults ); + + if ( init !== false ) { + new ColReorder( settings, opts ); + } + } +} ); + + +// API augmentation +$.fn.dataTable.Api.register( 'colReorder.reset()', function () { + return this.iterator( 'table', function ( ctx ) { + ctx._colReorder.fnReset(); + } ); +} ); + +$.fn.dataTable.Api.register( 'colReorder.order()', function ( set, original ) { + if ( set ) { + return this.iterator( 'table', function ( ctx ) { + ctx._colReorder.fnOrder( set, original ); + } ); + } + + return this.context.length ? + this.context[0]._colReorder.fnOrder() : + null; +} ); + +$.fn.dataTable.Api.register( 'colReorder.transpose()', function ( idx, dir ) { + return this.context.length && this.context[0]._colReorder ? + this.context[0]._colReorder.fnTranspose( idx, dir ) : + idx; +} ); + +$.fn.dataTable.Api.register( 'colReorder.move()', function( from, to, drop, invalidateRows ) { + if (this.context.length) { + this.context[0]._colReorder.s.dt.oInstance.fnColReorder( from, to, drop, invalidateRows ); + this.context[0]._colReorder._fnSetColumnIndexes(); + } + return this; +} ); + +$.fn.dataTable.Api.register( 'colReorder.enable()', function( flag ) { + return this.iterator( 'table', function ( ctx ) { + if ( ctx._colReorder ) { + ctx._colReorder.fnEnable( flag ); + } + } ); +} ); + +$.fn.dataTable.Api.register( 'colReorder.disable()', function() { + return this.iterator( 'table', function ( ctx ) { + if ( ctx._colReorder ) { + ctx._colReorder.fnDisable(); + } + } ); +} ); + + +return DataTable; +})); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/bulkImport.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/bulkImport.js index 9b22b6781ac..940ed8208e0 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/bulkImport.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/bulkImport.js @@ -51,8 +51,8 @@ $(function () { "stateSave": true, "autoWidth": false, "columns": [{ - "data": "tableId", - "width": "5%" + "data": "tableId", + "width": "5%" }, { "data": "fateId", diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactors.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactors.js index a68b2c2835e..11957b2e90b 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactors.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactors.js @@ -16,91 +16,27 @@ * specific language governing permissions and limitations * under the License. */ +/* JSLint global definitions */ +/*global + $, COMPACTOR_SERVER_PROCESS_VIEW, getCompactorsView, refreshServerInformation +*/ "use strict"; -var compactorsTable; +const htmlBanner = '#compactorsStatusBanner' +const htmlBannerMessage = '#compactors-banner-message' +const htmlTable = '#compactorsTable' +const visibleColumnFilter = (col) => col != "Server Type"; -/** - * Shows a red banner with the given message - */ -function showCompactorsBanner(message) { - $('#compactors-banner-message') - .removeClass('alert-warning') - .addClass('alert-danger') - .text(message); - $('#compactorsStatusBanner').show(); -} - -/** - * Show the error banner when there are no compactors - */ -function updateCompactorsBanner(compactors) { - if (!Array.isArray(compactors) || compactors.length === 0) { - showCompactorsBanner('No compactors are currently registered.'); - } else { - $('#compactorsStatusBanner').hide(); - } +function refresh() { + refreshServerInformation(getCompactorsView, htmlTable, COMPACTOR_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); } $(function () { - // display datatables errors in the console instead of in alerts - $.fn.dataTable.ext.errMode = 'throw'; - - compactorsTable = $('#compactorsTable').DataTable({ - "autoWidth": false, - "ajax": function (data, callback) { - $.ajax({ - "url": contextPath + 'rest-v2/ec/compactors', - "method": 'GET' - }).done(function (response) { - var compactors = Array.isArray(response.compactors) ? response.compactors : []; - updateCompactorsBanner(compactors); - callback({ - "data": compactors - }); - }).fail(function () { - showCompactorsBanner('Unable to retrieve compactor status.'); - callback({ - "data": [] - }); - }); - }, - "stateSave": true, - "dom": 't<"align-left"l>p', - "columnDefs": [{ - "targets": "duration", - "render": function (data, type, row) { - if (type === 'display') data = timeDuration(data); - return data; - } - }, - { - "targets": "date", - "render": function (data, type, row) { - if (type === 'display') data = dateFormat(data); - return data; - } - } - ], - "columns": [{ - "data": "server" - }, - { - "data": "groupName" - }, - { - "data": "lastContact" - } - ] + sessionStorage[SCAN_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null }); -}); - -function refreshCompactors() { - if (compactorsTable) { - ajaxReloadTable(compactorsTable); - } -} -function refresh() { - refreshCompactors(); -} + refreshServerInformation(getCompactorsView, htmlTable, COMPACTOR_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); +}); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js index 947b8b943d4..fe0e0dce89b 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js @@ -29,6 +29,12 @@ var SIZE_SUFFIX = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']; const REST_V2_PREFIX = contextPath + 'rest-v2'; const MANAGER_GOAL_STATE_METRIC = 'accumulo.manager.goal.state'; +const COMPACTOR_SERVER_PROCESS_VIEW = 'compactorsView'; +const GC_SERVER_PROCESS_VIEW = 'gcView'; +const MANAGER_SERVER_PROCESS_VIEW = 'managerssView'; +const SCAN_SERVER_PROCESS_VIEW = 'sserversView'; +const TABLET_SERVER_PROCESS_VIEW = 'tserversView'; + // Override Length Menu options for dataTables if ($.fn && $.fn.dataTable) { $.extend(true, $.fn.dataTable.defaults, { @@ -399,22 +405,27 @@ function getManager() { } /** - * Extracts the manager goal state from a metrics array. + * Gets the manager goal state from the cached manager response, if available. * - * @param {array} metrics Metric list from rest-v2/manager or rest-v2/manager/metrics * @return {string|null} Manager goal state (CLEAN_STOP, SAFE_MODE, NORMAL) or null */ -function getManagerGoalStateFromMetrics(metrics) { - if (!Array.isArray(metrics)) { - console.error('Metrics is not an array:', metrics); - return null; - } - const metric = metrics.find(m => m?.name === MANAGER_GOAL_STATE_METRIC); - if (!metric || typeof metric.value !== 'number') { - console.debug('Manager goal state metric not found or invalid:', metric); +function getManagerGoalStateFromSession() { + var mgrs = getStoredRows(MANAGER_SERVER_PROCESS_VIEW); + if (!Array.isArray(mgrs) || mgrs.length === 0) { + console.debug('No manager data in session storage. Returning null.'); return null; } - switch (metric.value) { + // There could be multiple managers that report different goal states. + // The goal state is stored in ZK, but it's eventually consistent. + // Use the lowest value seen as the current state for the Monitor + var goalState = 10; + $.each(mgrs, function (index, mgr) { + var stateVal = mgr[MANAGER_GOAL_STATE_METRIC]; + if (stateVal < goalState) { + goalState = stateVal; + } + }); + switch (goalState) { case 0: return 'CLEAN_STOP'; case 1: @@ -422,25 +433,7 @@ function getManagerGoalStateFromMetrics(metrics) { case 2: return 'NORMAL'; default: - return null; - } -} - -/** - * Gets the manager goal state from the cached manager response, if available. - * - * @return {string|null} Manager goal state (CLEAN_STOP, SAFE_MODE, NORMAL) or null - */ -function getManagerGoalStateFromSession() { - if (!sessionStorage?.manager) { - console.debug('No manager data in session storage. Returning null.'); - return null; - } - try { - const managerData = JSON.parse(sessionStorage.manager); - return getManagerGoalStateFromMetrics(managerData.metrics); - } catch (e) { - console.error('Failed to parse manager data from session storage', e); + console.debug('Manager goal state metric not found'); return null; } } @@ -693,13 +686,46 @@ function getDeployment() { } /** - * REST GET call for /sservers/view, + * REST GET call for /servers/view;serverType=COMPACTOR, + * stores it on a sessionStorage variable + */ +function getCompactorsView() { + return getJSONForTable(REST_V2_PREFIX + '/servers/view;serverType=COMPACTOR', COMPACTOR_SERVER_PROCESS_VIEW); +} + +/** + * REST GET call for /servers/view;serverType=GARBAGE_COLLECTOR, + * stores it on a sessionStorage variable + */ +function getGcView() { + return getJSONForTable(REST_V2_PREFIX + '/servers/view;serverType=GARBAGE_COLLECTOR', GC_SERVER_PROCESS_VIEW); +} + +/** + * REST GET call for /servers/view;serverType=MANAGER, + * stores it on a sessionStorage variable + */ +function getManagersView() { + return getJSONForTable(REST_V2_PREFIX + '/servers/view;serverType=MANAGER', MANAGER_SERVER_PROCESS_VIEW); +} + +/** + * REST GET call for /servers/view;serverType=SCAN_SERVER, * stores it on a sessionStorage variable */ function getSserversView() { - return getJSONForTable(REST_V2_PREFIX + '/sservers/view', 'sserversView'); + return getJSONForTable(REST_V2_PREFIX + '/servers/view;serverType=SCAN_SERVER', SCAN_SERVER_PROCESS_VIEW); } +/** + * REST GET call for /servers/view;serverType=TABLET_SERVER, + * stores it on a sessionStorage variable + */ +function getTserversView() { + return getJSONForTable(REST_V2_PREFIX + '/servers/view;serverType=TABLET_SERVER', TABLET_SERVER_PROCESS_VIEW); +} + + /** * REST GET call for /tservers/summary, * stores it on a sessionStorage variable diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/gc.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/gc.js index a8e6eb9c613..6c33007e80d 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/gc.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/gc.js @@ -16,114 +16,39 @@ * specific language governing permissions and limitations * under the License. */ +/* JSLint global definitions */ +/*global + $, GC_SERVER_PROCESS_VIEW, getGcView, refreshServerInformation +*/ "use strict"; -var gcTable; -/** - * Creates active compactions table - */ -$(function () { - // Create a table for compactions list - gcTable = $('#gcActivity').DataTable({ - "ajax": { - "url": contextPath + 'rest/gc', - "dataSrc": "stats" - }, - "stateSave": true, - "dom": 't<"align-left"l>p', - "columnDefs": [{ - "targets": "dateStarted", - "render": function (data, type, row) { - if (type === 'display') { - if (data === 0) data = 'Waiting'; - else if (data > 0) data = dateFormat(data); - else data = 'Error'; - } - return data; - } - }, - { - "targets": "dateFinished", - "render": function (data, type, row) { - if (type === 'display') { - if (data === 0) data = '—'; - else if (data > 0) data = dateFormat(data); - else data = 'Error'; - } - return data; - } - }, - { - "targets": "duration", - "render": function (data, type, row) { - if (type === 'display') { - if (data < 0) data = "Running"; - else data = timeDuration(data); - } - return data; - } - }, - { - "targets": "big-num", - "render": function (data, type, row) { - if (type === 'display') data = bigNumberForQuantity(data); - return data; - } - } - ], - "columns": [{ - "data": "type" - }, - { - "data": "started" - }, - { - "data": "finished" - }, - { - "data": "candidates" - }, - { - "data": "deleted" - }, - { - "data": "inUse" - }, - { - "data": "errors" - }, - { - "data": "duration" - }, - ] - }); - refreshGCTable(); -}); - -/** - * Used to redraw the page - */ -function refresh() { - refreshGCTable(); -} +const htmlBanner = '#gcStatusBanner' +const htmlBannerMessage = '#gc-banner-message' +const htmlTable = '#gc-server' +const visibleColumnFilter = (col) => col != "Server Type" && !col.startsWith("accumulo.gc."); -/** - * Generates the garbage collector table - */ -function refreshGCTable() { - var status = JSON.parse(sessionStorage.status).gcStatus; +const fileHtmlTable = '#gc-file' +const fileVisibleColumnFilter = (col) => col == "Last Contact" || col == "Resource Group" || + col == "Server Address" || (col.startsWith("accumulo.gc.") && !col.startsWith("accumulo.gc.wal.")); - if (status === 'ERROR') { - $('#gcBanner').show(); - $('#gcActivity').hide(); - } else { - $('#gcBanner').hide(); - $('#gcActivity').show(); - if (gcTable) gcTable.ajax.reload(null, false); // user paging is not reset on reload - } +const walHtmlTable = '#gc-wal' +const walVisibleColumnFilter = (col) => col == "Last Contact" || col == "Resource Group" || + col == "Server Address" || col.startsWith("accumulo.gc.wal."); +function refresh() { + refreshServerInformation(getGcView, htmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); + refreshServerInformation(getGcView, fileHtmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, fileVisibleColumnFilter); + refreshServerInformation(getGcView, walHtmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, walVisibleColumnFilter); } -function showBanner() { +$(function () { + sessionStorage[GC_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null + }); -} + refreshServerInformation(getGcView, htmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); + refreshServerInformation(getGcView, fileHtmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, fileVisibleColumnFilter); + refreshServerInformation(getGcView, walHtmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, walVisibleColumnFilter); +}); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/manager.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/manager.js index 7855b2a9a20..55d090f57f1 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/manager.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/manager.js @@ -18,100 +18,116 @@ */ /* JSLint global definitions */ /*global - $, document, sessionStorage, getManager, bigNumberForQuantity, - timeDuration, dateFormat, getStatus, ajaxReloadTable, getManagerGoalStateFromSession + $, sessionStorage, MANAGER_SERVER_PROCESS_VIEW, getManagersView, getStoredRows, getStoredStatus, + refreshTable, refreshBanner, showBannerError, getManagerGoalStateFromSession */ "use strict"; -var managerStatusTable, recoveryListTable, managerStatus; +const runningBanner = '#managerRunningBanner' +const htmlBanner = '#managerStatusBanner' +const htmlBannerMessage = '#manager-banner-message' +const managerStateBanner = '#managerStateBanner' +const managerStateBannerMessage = '#manager-state-message' +const htmlTable = '#managers' +const visibleColumnFilter = (col) => col != "Server Type" && !col.startsWith("accumulo.compaction.") && + !col.startsWith("accumulo.fate."); -function createManagerTable() { - // Generates the manager table - managerStatusTable = $('#managerStatusTable').DataTable({ - "ajax": function (data, callback, settings) { - $.ajax({ - url: contextPath + 'rest-v2/manager', - method: 'GET' - }).done(function (json) { - callback({ - "data": [json] - }); - }).fail(function (jqXHR, textStatus, errorThrown) { - // This is needed if the url is not yet available, but the manager is up. E.g., Short - // window where a 404 could occur, which would lead to DataTables error/alert w/out fail() - console.error("DataTables Ajax error :", errorThrown); - callback({ - "data": [] - }); - }); - }, - "stateSave": true, - "searching": false, - "paging": false, - "info": false, - "columnDefs": [{ - "targets": "timestamp", - "render": function (data, type) { - if (type === 'display') { - data = dateFormat(data); - } - return data; - } - }, - { - "targets": "metrics", - "orderable": false, - "render": function () { - return "Metrics"; - } - } - ], - "columns": [{ - "data": "host" - }, - { - "data": "resourceGroup" - }, - { - "data": "timestamp" - }, - { - "data": "metrics" - } - ] - }); +const fateHtmlTable = '#managers_fate' +const fateVisibleColumnFilter = (col) => col == "Last Contact" || col == "Resource Group" || + col == "Server Address" || col.startsWith("accumulo.fate."); + +const compactionHtmlTable = '#managers_compactions' +const compactionVisibleColumnFilter = (col) => col == "Last Contact" || col == "Resource Group" || + col == "Server Address" || col.startsWith("accumulo.compaction."); + +function updateManagerGoalStateBanner() { + const goalState = getManagerGoalStateFromSession(); + if (goalState === 'SAFE_MODE' || goalState === 'CLEAN_STOP') { + $(managerStateBannerMessage) + .removeClass('alert-danger alert-warning') + .addClass(goalState === 'CLEAN_STOP' ? 'alert-danger' : 'alert-warning') + .text('Manager goal state: ' + goalState); + $(managerStateBanner).show(); + } else { + $(managerStateBanner).hide(); + } } function refreshManagerBanners() { - // If manager status is error - if (managerStatus === 'ERROR') { + var managerRows = getStoredRows(MANAGER_SERVER_PROCESS_VIEW); + if (!Array.isArray(managerRows) || managerRows.length === 0) { // show the manager error banner and hide manager table - $('#managerRunningBanner').show(); - $('#managerStatusTable').hide(); - $('#managerStateBanner').hide(); + $(runningBanner).show(); + $(htmlTable).hide(); + $(fateHtmlTable).hide(); + $(compactionHtmlTable).hide(); } else { // otherwise, hide the error banner and show manager table - $('#managerRunningBanner').hide(); - $('#managerStatusTable').show(); - updateManagerGoalStateBanner(); + $(runningBanner).hide(); + $(fateHtmlTable).show(); + $(htmlTable).show(); + $(compactionHtmlTable).show(); } + updateManagerGoalStateBanner(); } -function updateManagerGoalStateBanner() { - getManager().always(function () { - const goalState = getManagerGoalStateFromSession(); - if (goalState === 'SAFE_MODE' || goalState === 'CLEAN_STOP') { - $('#manager-banner-message').text('Manager goal state: ' + goalState); - $('#managerStateBanner').show(); - } else { - $('#managerStateBanner').hide(); - } +function refresh() { + getManagersView().then(function () { + refreshTable(htmlTable, MANAGER_SERVER_PROCESS_VIEW, visibleColumnFilter); + refreshTable(fateHtmlTable, MANAGER_SERVER_PROCESS_VIEW, fateVisibleColumnFilter); + refreshTable(compactionHtmlTable, MANAGER_SERVER_PROCESS_VIEW, compactionVisibleColumnFilter); + refreshManagerBanners(); + refreshBanner(htmlBanner, htmlBannerMessage, getStoredStatus(MANAGER_SERVER_PROCESS_VIEW)); + }).fail(function () { + sessionStorage[MANAGER_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null + }); + refreshTable(htmlTable, MANAGER_SERVER_PROCESS_VIEW, visibleColumnFilter); + refreshTable(fateHtmlTable, MANAGER_SERVER_PROCESS_VIEW, fateVisibleColumnFilter); + refreshTable(compactionHtmlTable, MANAGER_SERVER_PROCESS_VIEW, compactionVisibleColumnFilter); + $(runningBanner).show(); + $(htmlTable).hide(); + $(fateHtmlTable).hide(); + $(compactionHtmlTable).hide(); + showBannerError(htmlBanner, htmlBannerMessage); }); } +$(function () { + sessionStorage[MANAGER_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null + }); + + refresh(); +}); + + + + + +// TODO: 6106 - left code commented for the recovery list table to be re-added + +/* JSLint global definitions */ +/*global + $, document, sessionStorage, getManager, bigNumberForQuantity, + timeDuration, dateFormat, getStatus, ajaxReloadTable, getManagerGoalStateFromSession +*/ +/* +"use strict"; + +var managerStatusTable, recoveryListTable, managerStatus; + + + +*/ /** * Populates tables with the new information */ +/* function refreshManagerTables() { getStatus().then(function () { managerStatus = JSON.parse(sessionStorage.status).managerStatus; @@ -126,7 +142,7 @@ function refreshManagerTables() { ajaxReloadTable(recoveryListTable); }); } - +*/ /* * The tables.ftl refresh function will do this functionality. * If tables are removed from Manager, uncomment this function. @@ -141,6 +157,7 @@ function refreshManagerTables() { /** * Creates initial tables */ +/* $(function () { getStatus().then(function () { @@ -202,3 +219,4 @@ $(function () { refreshManagerTables(); }); }); +*/ diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js new file mode 100644 index 00000000000..68eb3c7a9b7 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js @@ -0,0 +1,330 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* JSLint global definitions */ +/*global + $, sessionStorage, timeDuration, bigNumberForQuantity, bigNumberForSize, ajaxReloadTable, + renderActivityState, renderMemoryState, COLUMN_MAP +*/ +"use strict"; + +/** + * This file contains methods used to display tables on the Monitor's + * pages for server processes. The REST Endpoint /rest-v2/servers/view;serverType= + * returns a data structure that has the following format: + * + * { + * "data": [ + * { + * "colA", "valueA", + * "colB", "valueB" + * }, + * { + * "colA", "valueA", + * "colB", "valueB" + * } + * ], + * "columns": [ + * "colA", + * "colB" + * ], + * "status": { + * }, + * timestamp: long + * } + * + * The value for the 'columns' key is an array of strings, where each string is the name + * of a column. The value for the 'data' key is an array of objects. Each object represents + * a server and the fields in the object contain the fields specified in the 'columns' + * array and are in the same order. + * + * The 'columns' array is used to dynamically create table header rows in the html and + * the 'data' object is directly consumed by the DataTable where each object in the 'data' + * is a row in the table and each field in the object is a column. + * + * Modify the exclusion list in AbstractServer to remove columns from being returned from + * the AbstractServer.getMetrics RPC call. Be aware that other pages in the Monitor may + * use this information, not just the server process pages. The entrypoint for using + * the methods in this file is the refreshServerInformation method. To influence which columns + * are displayed on the server process pages use the column filter in that method. + */ + +var dataTableRefs = new Map(); + +/** + * This function returns the entire response from session storage + */ +function getStoredView(storageKey) { + if (!sessionStorage[storageKey]) { + return {}; + } + return JSON.parse(sessionStorage[storageKey]); +} + +/** + * This function returns the 'columns' array from the entire response + */ +function getStoredColumns(storageKey) { + var view = getStoredView(storageKey); + if (!Array.isArray(view.columns)) { + return []; + } + return view.columns; +} + +/** + * This function returns the 'data' array from the entire response + */ +function getStoredRows(storageKey) { + var view = getStoredView(storageKey); + if (!Array.isArray(view.data)) { + return []; + } + console.debug('table data: ' + JSON.stringify(view.data)); + return view.data; +} + +/** + * This function returns the stats object from the entire response + */ +function getStoredStatus(storageKey) { + var view = getStoredView(storageKey); + return view.status || null; +} + +/** + * This function is called as part of the DataTable initialization. + * It retrieves the columns from the response and sets the columns + * visibility based on the supplied filter. Setting the visiblity + * to false will hide the column in the display even if the table + * header HTML column exists. This allows us to create the table + * header rows without supplying the filter. + */ +function getDataTableCols(storageKey, visibleColumnFilter) { + var dataTableColumns = []; + var storedColumns = getStoredColumns(storageKey); + var visibleColumns = storedColumns.filter(visibleColumnFilter); + $.each(storedColumns, function (index, col) { + var v = visibleColumns.includes(col); + var colName = col.replaceAll(".", "\\."); + dataTableColumns.push({ + data: colName, + visible: v + }); + }); + console.debug('table columns: ' + JSON.stringify(dataTableColumns)); + return dataTableColumns; +} + +/** + * This function refreshes the table. It destroys the DataTable + * and clears the HTML table. Then it recreates the table header + * HTML elements from the columns, redefines the DataTable, + * and executes an ajax method to load the data. + */ +function refreshTable(table, storageKey, visibleColumnFilter) { + + // Destroy the DataTable + var dataTableRef = dataTableRefs.get(table); + if (dataTableRef != null) { + dataTableRef.destroy(); + } + + // Preserve table structure (e.g. the title) from the template but rebuild header and body content on refresh + $(table).find('thead').remove(); + $(table).find('tbody').remove(); + + // Create the HTML table columns + var htmlTableElement = $(table); + var thead = $(document.createElement("thead")); + var theadRow = $(document.createElement("tr")); + + var storedColumns = getStoredColumns(storageKey); + $.each(storedColumns, function (index, col) { + console.debug('Adding table header row for column: ' + JSON.stringify(col)); + if (COLUMN_MAP.has(col)) { + var mapping = COLUMN_MAP.get(col); + var th = $(document.createElement("th")); + th.addClass(mapping.classes); + th.text(mapping.header); + th.attr("title", mapping.description); + theadRow.append(th); + } else { + var th = $(document.createElement("th")); + th.text(col); + th.attr("title", "Unmapped column"); + theadRow.append(th); + } + }); + thead.append(theadRow); + htmlTableElement.append(thead); + htmlTableElement.append($(document.createElement('tbody'))); + + // Create the DataTable + dataTableRef = createDataTable(table, storageKey, visibleColumnFilter); + ajaxReloadTable(dataTableRef); + dataTableRefs.set(table, dataTableRef); +} + +/** + * This function shows a banner on the page if the + * status level is WARN in the Status object. + */ +function refreshBanner(banner, bannerMsg, status) { + if (status && status.level === 'WARN') { + $(bannerMsg) + .removeClass('alert-danger') + .addClass('alert-warning') + .text(status.message || 'WARN: server status warning.'); + $(banner).show(); + } else { + $(banner).hide(); + } +} + +function showBannerError(banner, bannerMsg) { + $(bannerMsg) + .removeClass('alert-warning') + .addClass('alert-danger') + .text('ERROR: unable to retrieve server status.'); + $(banner).show(); +} + +/** + * This function refreshes the table and banner, showing an + * empty table and error banner if not successful + * + * callback - the method to use to invoke the REST API call to get the data + * table - reference to HTML table object in which to create table header columns + * storageKey - the session storage key for the data returned from the REST API + * banner - reference to the HTML table that displays a banner + * bannerMsg - reference to the HTML object that is the banner + * visibleColumnFilter - filter to apply to columns to determine which are displayed + */ +function refreshServerInformation(callback, table, storageKey, banner, bannerMsg, visibleColumnFilter) { + callback().then(function () { + refreshTable(table, storageKey, visibleColumnFilter); + refreshBanner(banner, bannerMsg, getStoredStatus(storageKey)); + }).fail(function () { + sessionStorage[storageKey] = JSON.stringify({ + data: [], + columns: [], + status: null + }); + refreshTable(table, storageKey, visibleColumnFilter); + showBannerError(banner, bannerMsg); + }); +} + +/** + * This function creates the DataTable using the 'data' and + * 'columns' from the response object. It also defines how + * certain columns should be rendered based on the columns + * css class. + */ +function createDataTable(table, storageKey, visibleColumnFilter) { + var dataTableRef = $(table).DataTable({ + "autoWidth": false, + "ajax": function (data, callback) { + callback({ + data: getStoredRows(storageKey) + }); + }, + "stateSave": true, + "colReorder": true, + "columnDefs": [{ + targets: '_all', + defaultContent: '-' + }, + { + "targets": "big-num", + "render": function (data, type) { + if (type === 'display') { + if (data === null || data === undefined) { + return '—'; + } + data = bigNumberForQuantity(data); + } + return data; + } + }, + { + "targets": "big-size", + "render": function (data, type) { + if (type === 'display') { + if (data === null || data === undefined) { + return '—'; + } + data = bigNumberForSize(data); + } + return data; + } + }, + { + "targets": "start-date", + "render": function (data, type, row) { + if (type === 'display') { + if (data === 0) data = 'Waiting'; + else if (data > 0) data = dateFormat(data); + else data = 'Error'; + } + return data; + } + }, + { + "targets": "end-date", + "render": function (data, type, row) { + if (type === 'display') { + if (data === 0) data = '—'; + else if (data > 0) data = dateFormat(data); + else data = 'Error'; + } + return data; + } + }, + { + "targets": "duration", + "render": function (data, type) { + if (type === 'display') { + data = timeDuration(data); + } + return data; + } + }, + { + "targets": "percent", + "render": function (data, type) { + if (type === 'display') { + data = Math.round(data * 100) + '%'; + } + return data; + } + }, + { + "targets": "idle-state", + "render": renderActivityState + }, + { + "targets": "memory-state", + "render": renderMemoryState + } + ], + "columns": getDataTableCols(storageKey, visibleColumnFilter) + }); + return dataTableRef; +} diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/sservers.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/sservers.js index 011ce89c5b2..eba0de1c13c 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/sservers.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/sservers.js @@ -18,173 +18,25 @@ */ /* JSLint global definitions */ /*global - $, sessionStorage, timeDuration, bigNumberForQuantity, bigNumberForSize, ajaxReloadTable, - getSserversView, renderActivityState, renderMemoryState + $, SCAN_SERVER_PROCESS_VIEW, getSserversView, refreshServerInformation */ "use strict"; -var sserversTable; -var SSERVERS_VIEW_SESSION_KEY = 'sserversView'; - -function getStoredView() { - if (!sessionStorage[SSERVERS_VIEW_SESSION_KEY]) { - return {}; - } - return JSON.parse(sessionStorage[SSERVERS_VIEW_SESSION_KEY]); -} - -function getStoredRows() { - var view = getStoredView(); - if (!Array.isArray(view.servers)) { - return []; - } - return view.servers; -} - -function getStoredStatus() { - var view = getStoredView(); - return view.status || null; -} - -function refreshScanServersTable() { - ajaxReloadTable(sserversTable); -} - -function refreshSserversBanner(status) { - if (status && status.level === 'WARN') { - $('#sservers-banner-message') - .removeClass('alert-danger') - .addClass('alert-warning') - .text(status.message || 'WARN: scan-server status warning.'); - $('#sserversStatusBanner').show(); - } else { - $('#sserversStatusBanner').hide(); - } -} - -function showSserversBannerError() { - $('#sservers-banner-message') - .removeClass('alert-warning') - .addClass('alert-danger') - .text('ERROR: unable to retrieve scan-server status.'); - $('#sserversStatusBanner').show(); -} - -function refreshScanServers() { - getSserversView().then(function () { - refreshScanServersTable(); - refreshSserversBanner(getStoredStatus()); - }).fail(function () { - sessionStorage[SSERVERS_VIEW_SESSION_KEY] = JSON.stringify({ - servers: [], - status: null - }); - refreshScanServersTable(); - showSserversBannerError(); - }); -} +const htmlBanner = '#sserversStatusBanner' +const htmlBannerMessage = '#sservers-banner-message' +const htmlTable = '#sservers' +const visibleColumnFilter = (col) => col != "Server Type"; function refresh() { - refreshScanServers(); + refreshServerInformation(getSserversView, htmlTable, SCAN_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); } $(function () { - sessionStorage[SSERVERS_VIEW_SESSION_KEY] = JSON.stringify({ - servers: [], + sessionStorage[SCAN_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], status: null }); - sserversTable = $('#sservers').DataTable({ - "autoWidth": false, - "ajax": function (data, callback) { - callback({ - data: getStoredRows() - }); - }, - "stateSave": true, - "columnDefs": [{ - "targets": "big-num", - "render": function (data, type) { - if (type === 'display') { - if (data === null || data === undefined) { - return '—'; - } - data = bigNumberForQuantity(data); - } - return data; - } - }, - { - "targets": "big-size", - "render": function (data, type) { - if (type === 'display') { - if (data === null || data === undefined) { - return '—'; - } - data = bigNumberForSize(data); - } - return data; - } - }, - { - "targets": "duration", - "render": function (data, type) { - if (type === 'display') { - data = timeDuration(data); - } - return data; - } - } - ], - "columns": [{ - "data": "host" - }, - { - "data": "resourceGroup" - }, - { - "data": "lastContact" - }, - { - "data": "openFiles" - }, - { - "data": "queries" - }, - { - "data": "scannedEntries" - }, - { - "data": "queryResults" - }, - { - "data": "queryResultBytes" - }, - { - "data": "busyTimeouts" - }, - { - "data": "reservationConflicts" - }, - { - "data": "zombieThreads" - }, - { - "data": "serverIdle", - "render": renderActivityState - }, - { - "data": "lowMemoryDetected", - "render": renderMemoryState - }, - { - "data": "scansPausedForMemory" - }, - { - "data": "scansReturnedEarlyForMemory" - } - ] - }); - - refreshScanServers(); + refreshServerInformation(getSserversView, htmlTable, SCAN_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); }); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/tservers.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/tservers.js index e03cff19b49..bfb45dab690 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/tservers.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/tservers.js @@ -23,6 +23,11 @@ */ "use strict"; +const htmlBanner = '#tserversStatusBanner' +const htmlBannerMessage = '#tservers-banner-message' +const htmlTable = '#tservers' +const visibleColumnFilter = (col) => col != "Server Type"; + var tserversTable; var recoveryList = []; @@ -61,18 +66,11 @@ function refreshRecoveryList() { }); } -/** - * Refreshes data in the tserver table - */ -function refreshTServersTable() { - refreshRecoveryList(); - ajaxReloadTable(tserversTable); -} - /** * Show a page banner that matches the tablet server status shown in the navbar. */ -function refreshTServersBanner(statusData) { +function refreshTServersBanner() { + var statusData = JSON.parse(sessionStorage.status); if (statusData.managerStatus === 'ERROR') { $('#tserversManagerBanner').show(); $('#tserversWarnBanner').hide(); @@ -95,165 +93,22 @@ function refreshTServersBanner(statusData) { } } -/** - * Makes the REST calls, generates the tables with the new information - */ -function refreshTServers() { - getStatus().then(function () { - var statusData = JSON.parse(sessionStorage.status); - var managerStatus = statusData.managerStatus; - refreshTServersBanner(statusData); - - if (managerStatus === 'ERROR') { - return; - } - - getTServers().then(function () { - refreshTServersTable(); - }); - }); -} -/** - * Used to redraw the page - */ function refresh() { - refreshTServers(); + refreshRecoveryList(); + refreshTServersBanner(); + refreshServerInformation(getTserversView, htmlTable, TABLET_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); } -/** - * Creates initial tables - */ $(function () { refreshRecoveryList(); - // Create a table for tserver list - tserversTable = $('#tservers').DataTable({ - "ajax": { - "url": contextPath + 'rest/tservers', - "dataSrc": "servers" - }, - "stateSave": true, - "columnDefs": [{ - "targets": "big-num", - "render": function (data, type) { - if (type === 'display') { - data = bigNumberForQuantity(data); - } - return data; - } - }, - { - "targets": "duration", - "render": function (data, type) { - if (type === 'display') { - data = timeDuration(data); - } - return data; - } - }, - { - "targets": "percent", - "render": function (data, type) { - if (type === 'display') { - data = Math.round(data * 100) + '%'; - } - return data; - } - }, - // ensure these 3 columns are sorted by the 2 numeric values that comprise the combined string - // instead of sorting them lexicographically by the string itself. - // Specifically: 'targets' column will use the values in the 'orderData' columns - - // scan column will be sorted by number of running, then by number of queued - { - "targets": [8], - "type": "numeric", - "orderData": [14, 15] - }, - // minor compaction column will be sorted by number of running, then by number of queued - { - "targets": [9], - "type": "numeric", - "orderData": [16, 17] - }, - ], - "columns": [{ - "data": "hostname", - "type": "html", - "render": function (data, type, row) { - if (type === 'display') { - data = '' + row.hostname + ''; - } - return data; - } - }, - { - "data": "tablets" - }, - { - "data": "lastContact" - }, - { - "data": "responseTime" - }, - { - "data": "entries" - }, - { - "data": "ingest" - }, - { - "data": "query" - }, - { - "data": "holdtime" - }, - { - "data": "scansCombo" - }, - { - "data": "minorCombo" - }, - { - "data": "indexCacheHitRate" - }, - { - "data": "dataCacheHitRate" - }, - { - "data": "osload" - }, - { - "data": "scansRunning", - "visible": false - }, - { - "data": "scansQueued", - "visible": false - }, - { - "data": "minorRunning", - "visible": false - }, - { - "data": "minorQueued", - "visible": false - } - ], - "rowCallback": function (row, data, index) { - // reset background of each row - $(row).css('background-color', ''); - - // if the curent hostname is in the reovery list - if (serverIsInRecoveryList(data)) { - // highlight the current row - console.log('Highlighting row index:' + index + ' tserver:' + data.hostname); - $(row).css('background-color', 'gold'); - } - } + sessionStorage[TABLET_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null }); - refreshTServers(); + refreshServerInformation(getTserversView, htmlTable, TABLET_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); }); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/compactors.ftl b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/compactors.ftl index 3223bd1b3eb..b6a3e883172 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/compactors.ftl +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/compactors.ftl @@ -18,27 +18,15 @@ under the License. --> -
-
-

${title}

-
-
- - - - - - - -
Compactors   - + Compactors
+ The following Compactors reported status.
ServerGroupLast Contact
diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl index 24cf215268b..1f26bfdc496 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl @@ -34,16 +34,21 @@ + + + + +