Full Code of games647/LagMonitor for AI

main a9a9817df967 cached
81 files
302.5 KB
70.0k tokens
510 symbols
1 requests
Download .txt
Showing preview only (330K chars total). Download the full file or copy to clipboard to get everything.
Repository: games647/LagMonitor
Branch: main
Commit: a9a9817df967
Files: 81
Total size: 302.5 KB

Directory structure:
gitextract_smk98iqd/

├── .github/
│   └── dependabot.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── pom.xml
└── src/
    ├── main/
    │   ├── java/
    │   │   └── com/
    │   │       └── github/
    │   │           └── games647/
    │   │               └── lagmonitor/
    │   │                   ├── LagMonitor.java
    │   │                   ├── MethodMeasurement.java
    │   │                   ├── NativeManager.java
    │   │                   ├── Pages.java
    │   │                   ├── command/
    │   │                   │   ├── EnvironmentCommand.java
    │   │                   │   ├── GraphCommand.java
    │   │                   │   ├── HelpCommand.java
    │   │                   │   ├── LagCommand.java
    │   │                   │   ├── MbeanCommand.java
    │   │                   │   ├── MonitorCommand.java
    │   │                   │   ├── NativeCommand.java
    │   │                   │   ├── NetworkCommand.java
    │   │                   │   ├── PaginationCommand.java
    │   │                   │   ├── StackTraceCommand.java
    │   │                   │   ├── VmCommand.java
    │   │                   │   ├── dump/
    │   │                   │   │   ├── DumpCommand.java
    │   │                   │   │   ├── FlightCommand.java
    │   │                   │   │   ├── HeapCommand.java
    │   │                   │   │   └── ThreadCommand.java
    │   │                   │   ├── minecraft/
    │   │                   │   │   ├── PingCommand.java
    │   │                   │   │   ├── SystemCommand.java
    │   │                   │   │   ├── TPSCommand.java
    │   │                   │   │   └── TasksCommand.java
    │   │                   │   └── timing/
    │   │                   │       ├── PaperTimingsCommand.java
    │   │                   │       ├── SpigotTimingsCommand.java
    │   │                   │       ├── Timing.java
    │   │                   │       └── TimingCommand.java
    │   │                   ├── graph/
    │   │                   │   ├── ClassesGraph.java
    │   │                   │   ├── CombinedGraph.java
    │   │                   │   ├── CpuGraph.java
    │   │                   │   ├── GraphRenderer.java
    │   │                   │   ├── HeapGraph.java
    │   │                   │   └── ThreadsGraph.java
    │   │                   ├── listener/
    │   │                   │   ├── BlockingConnectionSelector.java
    │   │                   │   ├── GraphListener.java
    │   │                   │   ├── PageManager.java
    │   │                   │   └── ThreadSafetyListener.java
    │   │                   ├── logging/
    │   │                   │   ├── ForwardLogService.java
    │   │                   │   └── ForwardingLoggerFactory.java
    │   │                   ├── ping/
    │   │                   │   ├── PaperPing.java
    │   │                   │   ├── PingFetcher.java
    │   │                   │   ├── ReflectionPing.java
    │   │                   │   └── SpigotPing.java
    │   │                   ├── storage/
    │   │                   │   ├── MonitorSaveTask.java
    │   │                   │   ├── NativeSaveTask.java
    │   │                   │   ├── PlayerData.java
    │   │                   │   ├── Storage.java
    │   │                   │   ├── TPSSaveTask.java
    │   │                   │   └── WorldData.java
    │   │                   ├── task/
    │   │                   │   ├── IODetectorTask.java
    │   │                   │   ├── MonitorTask.java
    │   │                   │   ├── PingManager.java
    │   │                   │   └── TPSHistoryTask.java
    │   │                   ├── threading/
    │   │                   │   ├── BlockingActionManager.java
    │   │                   │   ├── BlockingSecurityManager.java
    │   │                   │   ├── Injectable.java
    │   │                   │   └── PluginViolation.java
    │   │                   ├── traffic/
    │   │                   │   ├── CleanUpTask.java
    │   │                   │   ├── Reflection.java
    │   │                   │   ├── TinyProtocol.java
    │   │                   │   └── TrafficReader.java
    │   │                   └── util/
    │   │                       ├── JavaVersion.java
    │   │                       ├── LagUtils.java
    │   │                       └── RollingOverHistory.java
    │   └── resources/
    │       ├── META-INF/
    │       │   └── services/
    │       │       └── org.slf4j.spi.SLF4JServiceProvider
    │       ├── config.yml
    │       ├── create.sql
    │       ├── default.jfc
    │       └── plugin.yml
    └── test/
        └── java/
            └── com/
                └── github/
                    └── games647/
                        └── lagmonitor/
                            ├── LagMonitorTest.java
                            ├── RollingOverHistoryTest.java
                            ├── listener/
                            │   └── BlockingConnectionSelectorTest.java
                            └── util/
                                ├── JavaVersionTest.java
                                └── LagUtilsTest.java

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: maven
  directory: "/"
  schedule:
    interval: monthly
  open-pull-requests-limit: 10
  ignore:
  - dependency-name: com.github.oshi:oshi-demo
    versions:
    - "> 5.2.2, < 5.3"
  - dependency-name: com.github.oshi:oshi-demo
    versions:
    - "> 5.3.4, < 5.4"
  - dependency-name: io.netty:netty-codec
    versions:
    - "> 4.1.45.Final"
  - dependency-name: mysql:mysql-connector-java
    versions:
    - "> 5.1.48"
  - dependency-name: org.apache.maven.plugins:maven-shade-plugin
    versions:
    - "> 3.2.3, < 3.3"
  - dependency-name: org.apache.maven.plugins:maven-surefire-plugin
    versions:
    - "> 2.22.0, < 2.23"
  - dependency-name: org.mockito:mockito-junit-jupiter
    versions:
    - "> 3.4.4, < 3.5"
  - dependency-name: org.mockito:mockito-junit-jupiter
    versions:
    - ">= 3.5.a, < 3.6"
  - dependency-name: pl.project13.maven:git-commit-id-plugin
    versions:
    - "> 4.0.0, < 4.1"
  - dependency-name: com.github.oshi:oshi-demo
    versions:
    - 5.4.1
    - 5.5.0
    - 5.5.1
    - 5.6.1
    - 5.7.0
  - dependency-name: org.mockito:mockito-junit-jupiter
    versions:
    - 3.7.7
    - 3.8.0


================================================
FILE: .gitignore
================================================
# Eclipse
.classpath
.project
.settings/

# NetBeans
nbproject/
nb-configuration.xml

# IntelliJ
*.iml
*.ipr
*.iws
.idea/

# Maven
target/
pom.xml.versionsBackup

# Gradle
.gradle

# Ignore Gradle GUI config
gradle-app.setting

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

# various other potential build files
build/
bin/
dist/
manifest.mf
*.log

# Vim
.*.sw[a-p]

# virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

# Mac filesystem dust
.DS_Store



================================================
FILE: .travis.yml
================================================
# Use https://travis-ci.org/ for automatic testing

# speed up testing https://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/
sudo: false

# This is a java project
language: java

script: mvn test -B

jdk:
  - oraclejdk8
  - oraclejdk9



================================================
FILE: CHANGELOG.md
================================================
# Changelog

## 1.17.1

* Make the link for monitoring clickable
* Improve performance of /tasks command using MethodHandles
* Fail safely on native library errors

## 1.17

* Use faster MethodHandles to lookup the player ping
* Close all resources after calculating folder size
* Verify if JFR methods are available in the current VM
* Enable native driver by default if available
* Hide vanished players from the ping command
* Merge Paper and Spigot Timings parsing into one command
* Improve wording for thread block or safety warnings:
    * If you think something is missing or could be described better, please make a pull request.
* Add Thread safety checks for command events too
    * Ref: https://www.spigotmc.org/threads/plugins-triggering-commands-async.31815/
* Add statically compiled java version checker
* Add a lot of more native Hardware and Software details:
    * Sensors (voltage, fan speed)
    * Motherboard
    * Networking
    * CPU
    * Java properties
    * Process
    * User
* Replace outdated sigar library with oshi
* Validate input for average comparison (Fixes #37)

## 1.16

* Use Bukkit's internal method to find the plugin owner
* Fix checking vanilla command class check if we found an obfuscated plugin
* Dynamically adjust text padding for graphs
* Fix invalid threads graph name
* Count the read/write of all disks
* Use migration file creating MySQL table
* Use MEDIUMINT for os with > 64GB of ram (Related #33)
* Fix folder size calculation
* Fix free ram calculation (Fixes #33)
* Delay ping fetching on player join, because the first ping request is very inaccurate.

## 1.15

* Better url output for blocking http actions
* Query the partition and not the filesystem for the reads/writes
* Add linux distribution info
* Fix total file system space

## 1.14.3

* Refactor plugin detection. Now it skips the first x entries of LagMonitor until it finds another class loader.

## 1.14.2

* Fix plugin name detection

## 1.14.1

* Fix 1.12 support

## 1.14

* Show file system type for the native command
* Replace the /paper command alias with /paper-timing to prevent overrides by Paper itself

## 1.13

* Whitelist vanilla commands

## 1.12

* Filter invalid ping values
* Migrate to Java 7 Path API for faster free space and other file system lookups

## 1.11.10

* Better block message descriptions

## 1.11.9

* Fix parsing hover event for 1.8 clients
* Wrap to a new line only after the word
* Use .spigot() for sendMessage(BaseComponent) for backwards compatibility

## 1.11.8

* Fix map listener for older minecraft version (with only one item-hand)

## 1.11.7

* Removed old debug code
* Fix variable replacing in the help command

## 1.11.6

* Fixed memory leak for player pings on player quit

## 1.11.5

* Added a help page
* Added new permission lagmonitor.command.help

## 1.11.4

* Fix users don't receive a map on graph command
* Display error message for untracked ping players
* Fail silently if the jfc file already exists

## 1.11.3

* Fix detecting socket connections (socket-block-detection) if the default proxy is null

## 1.11.2

* Optimize plugin violations handling
* Fix security manager spams if enabled
* Fix log caused methods only once even if it's disabled

## 1.11.1

* Add missing uri to the connection selector
* Fix plugin name detection and thread-safety (Fixes #17)

## 1.11

* Added sigar as fallback when Oracle API isn't available (com.sun.management.OperatingSystemMXBean)

## 1.10.1

* Fix thread safety check

## 1.10

* Add hideStacktrace config property, which shows only two lines
* Add oncePerPlugin config property which report it only one time per startup and plugin
* Add a way to find the plugin source. [Experimental]

## 1.9.1

* Allow blocking actions on server startup (Fixes #15)
* Clarify blocking action message
* Upgrade to Java 8 (requires now Java 8)

## 1.9

* Add monitor pastes to https://paste.enginehub.org/ - Please support for this awesome service and please do not spam it
* Fix showing duplicate http blocking messages, because a http connection is also a socket connection
* Fix showing stacktrace on blocking action

## 1.8

* Add /lagpage < save >  and /lagpage < all >

## 1.7.2

* Fix traffic reader storage save
* Warn users who still use the outdated Java 7 to upgrade to a newer version

## 1.7

* Fail safely on an error for traffic reader
* Add configurable table prefix
* Add debug code if the storage insert failed

## 1.6

* Added whitelist for certain commands for specific users

## 1.5

* Added a faster and less error-prone blocking http detection

## 1.4

* Added monitoring to a MySQL database
* Added a unsupported java vendor hint to heap and thread dumps
* Speed up the native command by loading the native driver only on plugin load

## 1.3.2

* Fix command permission for /ping player

## 1.3.1

* Fix class not found in paper spigot timings parser if user is using normal spigot

## 1.3

* Added PaperTimings head data
* Added percent values to the paper spigot timings
* Fixed combined plugin name
* Fixed unknown entries in paper spigot timings parser
* Fixed missing total second head data in spigot timings parser
* Fixed pagination error from the last page

## 1.2

* Added support for Java Flight Recorder dump
* Added default configuration file for flight recorder
* Fixed permission of lagpage command has the paper command permission

## 1.1

* Added thread dump to file option /thread dump
* Added heap dump to file option /heap dump
* Fix pagination error if the user is requesting a too high page number

## 1.0

* Added plugin injection (commands, listener and tasks)
* Added pagination
* Added /heap command for heap dumps
* Added world size to the system command
* Added tile entities count to the system command
* Added security manager for more efficient blocking checks
* Added combined graphs example: /graph cpu heap threads
* Added check if timings is enabled for Paper servers
* Improved performance of commands by caching them with the pagination
* Optimize Spigot timings parser

## 0.7

* Added /lag alias for the /tpshistory command
* Added swap to the environment command
* Added tasks command
* Added /vm command for class loading, garbage collectors, vm specifications
* Added basic Paper timings parser
* Added load average to the environment command
* Moved Java version to the vm command
* Optimized thread locking in monitor/profiler for better performance

## 0.6

* Added /native command to query native data like OS uptime, Network adapter speed, CPU MHZ, ...
* Added startup parameters to the system command
* Added thread-safety check
* Added blocking, waiting, sleeping check
* Added Thread id to the threads command
* Improved readability for tpshistory command in console
* Fixed very low tps value displayed as full tps
* Fixed scrolling tpsHistory
* Fixed NPE on plugin load at runtime
* Fixes ClassNotFoundException on reload if traffic reader is activated
* Fixed rounding issues for the average ping
* Fixed cleanup of monitor task on plugin disable

## 0.5

* Added Ping History -> displays average ping now
* Added traffic counter
* Added config
* Reduce memory usage by getting the stacktrace of only one thread
* Fixed thread safety

## 0.4

* Added world info to the system command
* Added lazy loading for thread monitor to reduce memory usage
* Added worlds, players and plugins count to the system command
* Added samples count for thread monitor
* Improved tons of command styling
* Fixed thread safety
* Fixed free memory value
* Fixed memory leak for thread monitor
* Fixed ping method only displaying the own ping

## 0.3

* Fixed: max memory output in the /system command
* Added color highlighting for performance intensive tasks in the timings report
* Fixed timings output
* Added warning if timings are deactivated
* Added classes graph
* Added command completion for all commands
* Updated to Minecraft 1.9
* Added missing permission node for /ping [player] to the plugin.yml

## 0.2

* Added environment command
* Added server version to the system command
* Added more graphs (Threads, CPU usage)
* Fixed CPU usage value
* Improved Command output styling
* Reduced delay start of ticks per second task

## 0.1.1

* Added command permissions
* Added online check for the ping command


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016-2018

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: README.md
================================================
# LagMonitor

## Description

Gives you the possibility to monitor your server performance. This plugin is based on the powerful tools VisualVM and
Java Mission Control, both provided by Oracle. This plugin gives you the possibility to use the features provided by
these tools also in Minecraft itself. This might be useful for server owners/administrators who cannot use the tools.

Furthermore, it is especially made for Minecraft itself. So you can also check your TPS (Ticks per second), player ping,
server timings and so on.

## Features

* Player ping
* Offline Java version checker
* Thread safety checks
* Many details about your setup like Hardware (Disk, Processor, ...) and about your OS 
* Sample CPU usage
* Analyze RAM usage
* Access to Stacktraces of running threads
* Shows your ticks per second with history
* Shows system performance usage
* Visual graphs in-game
* In-game timings viewer
* Access to Java environment variables (mbeans)
* Plugin specific profiles
* Blocking operations on the main thread check
* Make Heap and Thread dumps
* Create Java Flight Recorder dump and analyze it later on your own computer
* Log the server performance into a MySQL/MariaDB database

## Requirements

* Java 8+
* Spigot 1.8.8+ or a fork of it (ex: Paper)

## Permissions

lagmonitor.* - Access to all LagMonitor features

lagmonitor.commands.* - Access to all commands

### All command permissions

* lagmonitor.command.ping
* lagmonitor.command.ping.other
* lagmonitor.command.stacktrace
* lagmonitor.command.thread
* lagmonitor.command.tps
* lagmonitor.command.mbean
* lagmonitor.command.system
* lagmonitor.command.environment
* lagmonitor.command.timing
* lagmonitor.command.monitor
* lagmonitor.command.graph
* lagmonitor.command.native
* lagmonitor.command.vm
* lagmonitor.command.network
* lagmonitor.command.tasks
* lagmonitor.command.heap
* lagmonitor.command.jfr

## Commands

    /ping - Gets your server ping
    /ping <player> - Gets the ping of the selected player
    /stacktrace - Gets the execution stacktrace of the current thread
    /stacktrace <threadName> - Gets the execution stacktrace of selected thread
    /thread - Outputs all running threads with their current state
    /tpshistory - Outputs the current tps
    /mbean - List all available mbeans (java environment information, JMX)
    /mbean <beanName> - List all available attributes of this mbean
    /mbean <beanName> <attribute> - Outputs the value of this attribute
    /system - Gives you some general information (Minecraft server related)
    /env - Gives you some general information (OS related)
    /timing - Outputs your server timings ingame
    /monitor [start|stop|paste] - Monitors the CPU usage of methods
    /graph [heap|cpu|threads|classes] - Gives you visual graph about your server (currently only the heap usage)
    /native - Gives you some native os information
    /vm - Outputs vm specific information like garbage collector, class loading or vm specification
    /network - Shows network interface configuration
    /tasks - Information about running and pending tasks
    /heap - Heap dump about your current memory
    /lagpage <next|prev|pageNumber|save|all> - Pagination command for the current pagination session
    /jfr <start|stop|dump> - Manages the Java Flight Recordings of the native Java VM. It gives you much more detailed
        information including network communications, file read/write times, detailed heap and thread data, ...

## Development builds

Development builds of this project can be acquired at the provided CI (continuous integration) server. It contains the
latest changes from the Source-Code in preparation for the following release. This means they could contain new
features, bug fixes and other changes since the last release.

Nevertheless builds are only tested using a small set of automated and a few manual tests. Therefore they **could**
contain new bugs and are likely to be less stable than released versions.

https://ci.codemc.org/job/Games647/job/LagMonitor/changes

## Network requests

This plugin performs network requests to:

* https://paste.enginehub.org - uploading monitor paste command outputs

## Reproducible builds

This project supports reproducible builds for enhanced security. In short, this means that the source code matches
the generated built jar file. Outputs could vary by operating system (line endings), different JDK
versions and build timestamp. You can extract this using 
[build-info](https://github.com/apache/maven-studies/tree/maven-buildinfo-plugin). Once you have
the configuration to use the same line endings and JDK version, you can use the following command
to inject a custom build timestamp to complete the configuration.

`mvn clean install -Dproject.build.outputTimestamp=DATE`

## Images

### Heap command
![heap command](https://i.imgur.com/AzDwYxq.png)

### Timing command
![timing command](https://i.imgur.com/wAxnIxt.png)

### CPU Graph (blue=process, yellow=system) - Process load
![cpu graph](https://i.imgur.com/DajnZmP.png)

### Stacktrace and Threads command
![stacktrace and threads](https://i.imgur.com/XY7r9wz.png)

### Ping Command
![ping command](https://i.imgur.com/LITJKWw.png)

### Thread Sampler (Monitor command)
![thread sample](https://i.imgur.com/OXOakN6.png)

### System command
![system command](https://i.imgur.com/hrIV6bW.png)

### Environment command
![environment command](https://i.imgur.com/gQwr126.png)

### Heap usage graph (yellow=allocated, blue=used)
![heap usage map](https://i.imgur.com/Yiz9h6G.png)


================================================
FILE: pom.xml
================================================
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.games647</groupId>
    <!--This have to be in lowercase because it's used by plugin.yml-->
    <artifactId>lagmonitor</artifactId>
    <packaging>jar</packaging>

    <name>LagMonitor</name>
    <version>1.17.3</version>

    <url>https://dev.bukkit.org/bukkit-plugins/LagMonitor/</url>
    <description>
        Monitors your Minecraft server for Lags
    </description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <maven.compiler.source>10</maven.compiler.source>
        <maven.compiler.target>10</maven.compiler.target>
        <maven.compiler.release>10</maven.compiler.release>

        <!-- 5.7.5 uses JNA 5.12.1, so this needs to adjusted in the plugin.yml -->
        <junit.jupiter.version>5.9.2</junit.jupiter.version>
        <oshi.version>6.4.1</oshi.version>

        <spigotApi>1.19.4-R0.1-SNAPSHOT</spigotApi>
    </properties>

    <build>
        <defaultGoal>install</defaultGoal>
        <!--Just use the project name to replace an old version of the plugin if the user does only copy-paste-->
        <finalName>${project.name}</finalName>

        <plugins>
            <!-- Force an update to this plugin to allow reproducible builds -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <!-- According to the Maven wiki, we have to use at least 3.2 for reproducible builds -->
                <!-- https://maven.apache.org/guides/mini/guide-reproducible-builds.html -->
                <version>3.2.0</version>
            </plugin>

            <!-- Add libraries to the plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                    <relocations>
                        <relocation>
                            <pattern>oshi</pattern>
                            <shadedPattern>lagmonitor.oshi</shadedPattern>
                            <excludes>
                                <exclude>*.properties</exclude>
                                <exclude>oshi.architecture.properties</exclude>
                                <exclude>oshi.linux.filename.properties</exclude>
                                <exclude>oshi.macos.versions.properties</exclude>
                                <exclude>oshi.properties</exclude>
                                <exclude>oshi.vmmacaddr.properties</exclude>
                            </excludes>
                        </relocation>
                    </relocations>
                    <artifactSet>
                        <excludes>
                            <!-- Exclude native drivers by default for user security reasons -->
                            <exclude>net.java.dev.jna:jna</exclude>
                        </excludes>
                    </artifactSet>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <!--Expose git variables for version names-->
            <plugin>
                <groupId>pl.project13.maven</groupId>
                <artifactId>git-commit-id-plugin</artifactId>
                <version>4.9.10</version>
                <configuration>
                    <failOnNoGitDirectory>false</failOnNoGitDirectory>
                </configuration>
                <executions>
                    <execution>
                        <id>get-the-git-infos</id>
                        <goals>
                            <goal>revision</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <!-- Testing plugin-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>

        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <!--Replace variables-->
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

    <repositories>
        <!--Bukkit-Server-API -->
        <repository>
            <id>spigot-repo</id>
            <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
        </repository>

        <!--Paper repository-->
        <repository>
            <id>paper-repo</id>
            <url>https://repo.papermc.io/repository/maven-snapshots/</url>
        </repository>
    </repositories>

    <dependencies>
        <!--Server API for Paper Timings API-->
        <dependency>
            <groupId>io.papermc.paper</groupId>
            <artifactId>paper-api</artifactId>
            <version>${spigotApi}</version>
            <scope>provided</scope>
            <exclusions>
                <exclusion>
                    <groupId>net.md-5</groupId>
                    <artifactId>bungeecord-chat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--Server API for Spigot-->
        <dependency>
            <groupId>org.spigotmc</groupId>
            <artifactId>spigot-api</artifactId>
            <version>${spigotApi}</version>
            <scope>provided</scope>
        </dependency>

        <!-- Native data API -->
        <dependency>
            <groupId>com.github.oshi</groupId>
            <artifactId>oshi-demo</artifactId>
            <version>${oshi.version}</version>
            <!-- Excludes the libraries below, because we only need the DetectVm class which has no dependencies on
            the libraries below-->
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-databind</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.jfree</groupId>
                    <artifactId>jfreechart</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- JDK logging bridge to forward logging messages to the plugin logger -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>2.0.7</version>
        </dependency>

        <!-- Include core explicitly in order to ship the default configuration files in it -->
        <dependency>
            <groupId>com.github.oshi</groupId>
            <artifactId>oshi-core</artifactId>
            <version>${oshi.version}</version>
        </dependency>

        <!--MySQL driver that is included in Spigot-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.32</version>
            <scope>provided</scope>
        </dependency>

        <!--Netty work library of Minecraft - This is added to read the amount of bytes which are sent or received-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-codec</artifactId>
            <version>4.1.45.Final</version>
            <scope>provided</scope>
        </dependency>

        <!--JUnit 5-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>

        <!--Mocking library-->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>5.2.0</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>2.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>


================================================
FILE: src/main/java/com/github/games647/lagmonitor/LagMonitor.java
================================================
package com.github.games647.lagmonitor;

import com.github.games647.lagmonitor.command.EnvironmentCommand;
import com.github.games647.lagmonitor.command.GraphCommand;
import com.github.games647.lagmonitor.command.HelpCommand;
import com.github.games647.lagmonitor.command.MbeanCommand;
import com.github.games647.lagmonitor.command.MonitorCommand;
import com.github.games647.lagmonitor.command.NativeCommand;
import com.github.games647.lagmonitor.command.NetworkCommand;
import com.github.games647.lagmonitor.command.PaginationCommand;
import com.github.games647.lagmonitor.command.StackTraceCommand;
import com.github.games647.lagmonitor.command.VmCommand;
import com.github.games647.lagmonitor.command.dump.FlightCommand;
import com.github.games647.lagmonitor.command.dump.HeapCommand;
import com.github.games647.lagmonitor.command.dump.ThreadCommand;
import com.github.games647.lagmonitor.command.minecraft.PingCommand;
import com.github.games647.lagmonitor.command.minecraft.SystemCommand;
import com.github.games647.lagmonitor.command.minecraft.TPSCommand;
import com.github.games647.lagmonitor.command.minecraft.TasksCommand;
import com.github.games647.lagmonitor.command.timing.PaperTimingsCommand;
import com.github.games647.lagmonitor.command.timing.SpigotTimingsCommand;
import com.github.games647.lagmonitor.listener.BlockingConnectionSelector;
import com.github.games647.lagmonitor.listener.GraphListener;
import com.github.games647.lagmonitor.listener.PageManager;
import com.github.games647.lagmonitor.listener.ThreadSafetyListener;
import com.github.games647.lagmonitor.logging.ForwardingLoggerFactory;
import com.github.games647.lagmonitor.storage.MonitorSaveTask;
import com.github.games647.lagmonitor.storage.NativeSaveTask;
import com.github.games647.lagmonitor.storage.Storage;
import com.github.games647.lagmonitor.storage.TPSSaveTask;
import com.github.games647.lagmonitor.task.IODetectorTask;
import com.github.games647.lagmonitor.task.PingManager;
import com.github.games647.lagmonitor.task.TPSHistoryTask;
import com.github.games647.lagmonitor.threading.BlockingActionManager;
import com.github.games647.lagmonitor.threading.BlockingSecurityManager;
import com.github.games647.lagmonitor.threading.Injectable;
import com.github.games647.lagmonitor.traffic.TrafficReader;

import java.net.ProxySelector;
import java.nio.file.Files;
import java.sql.SQLException;
import java.time.Duration;
import java.util.Optional;
import java.util.Timer;
import java.util.logging.Level;

import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;

public class LagMonitor extends JavaPlugin {

    private static final int DETECTION_THRESHOLD = 10;
    private static final int HOURS_PER_DAY = 24;
    private static final int MINUTES_PER_HOUR = 60;
    private static final int SECONDS_PER_MINUTE = 60;

    private final BlockingActionManager actionManager = new BlockingActionManager(this);
    private final PageManager pageManager = new PageManager();
    private final TPSHistoryTask tpsHistoryTask = new TPSHistoryTask();
    private final NativeManager nativeData = new NativeManager(getLogger(), getDataFolder().toPath());

    private PingManager pingManager;
    private TrafficReader trafficReader;
    private Timer blockDetectionTimer;
    private Timer monitorTimer;

    @Override
    public void onLoad() {
        ForwardingLoggerFactory.PARENT_LOGGER = getLogger();

        nativeData.setupNativeAdapter();
    }

    @Override
    public void onEnable() {
        saveDefaultConfig();

        if (Files.notExists(getDataFolder().toPath().resolve("default.jfc"))) {
            saveResource("default.jfc", false);
        }

        try {
            pingManager = new PingManager(this);
        } catch (ReflectiveOperationException reflectiveEx) {
            getLogger().log(Level.SEVERE, "Cannot initialize ping manager", reflectiveEx);
        }

        //register schedule tasks
        BukkitScheduler scheduler = getServer().getScheduler();
        scheduler.runTaskTimer(this, tpsHistoryTask, 20L, TPSHistoryTask.RUN_INTERVAL);
        scheduler.runTaskTimer(this, pingManager, 20L, PingManager.PING_INTERVAL);

        //register listeners
        PluginManager pluginManager = getServer().getPluginManager();
        pluginManager.registerEvents(new GraphListener(), this);
        pluginManager.registerEvents(pageManager, this);
        pluginManager.registerEvents(pingManager, this);

        //add the player to the list in the case the plugin is loaded at runtime
        Bukkit.getOnlinePlayers().forEach(pingManager::addPlayer);

        if (getConfig().getBoolean("traffic-counter")) {
            try {
                trafficReader = new TrafficReader(this);
            } catch (Exception ex) {
                getLogger().log(Level.SEVERE, "Failed to initialize packet reader", ex);
            }
        }

        if (getConfig().getBoolean("thread-safety-check")) {
            pluginManager.registerEvents(new ThreadSafetyListener(actionManager), this);
        }

        if (getConfig().getBoolean("thread-block-detection")) {
            scheduler.runTask(this, () -> {
                blockDetectionTimer = new Timer(getName() + "-Thread-Blocking-Detection");
                IODetectorTask detectorTask = new IODetectorTask(actionManager, Thread.currentThread());
                blockDetectionTimer.scheduleAtFixedRate(detectorTask, DETECTION_THRESHOLD, DETECTION_THRESHOLD);
            });
        }

        if (getConfig().getBoolean("monitor-database")) {
            setupMonitoringDatabase();
        }

        if (getConfig().getBoolean("socket-block-detection")) {
            scheduler.runTask(this, () -> new BlockingConnectionSelector(actionManager).inject());
        }

        if (getConfig().getBoolean("securityMangerBlockingCheck")) {
            if (Runtime.version().feature() < 17) {
                scheduler.runTask(this, () -> new BlockingSecurityManager(actionManager).inject());
            }
        }

        registerCommands();
    }

    private void setupMonitoringDatabase() {
        try {
            String host = getConfig().getString("host");
            int port = getConfig().getInt("port");
            String database = getConfig().getString("database");
            boolean useSSL = getConfig().getBoolean("useSSL");

            String username = getConfig().getString("username");
            String password = getConfig().getString("password");
            String tablePrefix = getConfig().getString("tablePrefix");
            Storage storage = new Storage(getLogger(), host, port, database, useSSL, username, password, tablePrefix);
            storage.createTables();

            BukkitScheduler scheduler = getServer().getScheduler();
            scheduler.runTaskTimerAsynchronously(this, new TPSSaveTask(tpsHistoryTask, storage), 20L,
                     getConfig().getInt("tps-save-interval") * 20L);
            //this can run async because it runs independently of the main thread
            scheduler.runTaskTimerAsynchronously(this, new MonitorSaveTask(this, storage),
                    20L,getConfig().getInt("monitor-save-interval") * 20L);
            scheduler.runTaskTimerAsynchronously(this, new NativeSaveTask(this, storage),
                    20L,getConfig().getInt("native-save-interval") * 20L);
        } catch (SQLException sqlEx) {
            getLogger().log(Level.SEVERE, "Failed to setup monitoring database", sqlEx);
        }
    }

    @Override
    public void onDisable() {
        if (trafficReader != null) {
            trafficReader.close();
            trafficReader = null;
        }

        close(blockDetectionTimer);
        blockDetectionTimer = null;

        close(monitorTimer);
        monitorTimer = null;

        //restore the security manager
        SecurityManager securityManager = System.getSecurityManager();
        if (securityManager instanceof BlockingSecurityManager) {
            ((Injectable) securityManager).restore();
        }

        ProxySelector proxySelector = ProxySelector.getDefault();
        if (proxySelector instanceof BlockingConnectionSelector) {
            ((Injectable) proxySelector).restore();
        }
    }

    private void close(Timer timer) {
        if (timer != null) {
            timer.cancel();
            timer.purge();
        }
    }

    public PageManager getPageManager() {
        return pageManager;
    }

    public Timer getMonitorTimer() {
        return monitorTimer;
    }

    public void setMonitorTimer(Timer monitorTimer) {
        this.monitorTimer = monitorTimer;
    }

    public TrafficReader getTrafficReader() {
        return trafficReader;
    }

    public TPSHistoryTask getTpsHistoryTask() {
        return tpsHistoryTask;
    }

    public Optional<PingManager> getPingManager() {
        return Optional.ofNullable(pingManager);
    }

    public NativeManager getNativeData() {
        return nativeData;
    }

    private void registerCommands() {
        getCommand(getName()).setExecutor(new HelpCommand(this));

        getCommand("ping").setExecutor(new PingCommand(this));
        getCommand("stacktrace").setExecutor(new StackTraceCommand(this));
        getCommand("thread").setExecutor(new ThreadCommand(this));
        getCommand("tpshistory").setExecutor(new TPSCommand(this));
        getCommand("mbean").setExecutor(new MbeanCommand(this));
        getCommand("system").setExecutor(new SystemCommand(this));
        getCommand("env").setExecutor(new EnvironmentCommand(this));
        getCommand("monitor").setExecutor(new MonitorCommand(this));
        getCommand("graph").setExecutor(new GraphCommand(this));
        getCommand("native").setExecutor(new NativeCommand(this));
        getCommand("vm").setExecutor(new VmCommand(this));
        getCommand("tasks").setExecutor(new TasksCommand(this));
        getCommand("heap").setExecutor(new HeapCommand(this));
        getCommand("lagpage").setExecutor(new PaginationCommand(this));
        getCommand("jfr").setExecutor(new FlightCommand(this));
        getCommand("network").setExecutor(new NetworkCommand(this));

        PluginCommand timing = getCommand("timing");
        try {
            //paper moved to class to package co.aikar.timings
            Class.forName("co.aikar.timings.Timing");
            timing.setExecutor(new PaperTimingsCommand(this));
        } catch (ClassNotFoundException e) {
            timing.setExecutor(new SpigotTimingsCommand(this));
        }
    }

    public static String formatDuration(Duration duration) {
        long seconds = duration.getSeconds();
        return String.format("'%d' days '%d' hours '%d' minutes '%d' seconds'",
                duration.toDays(),
                duration.toHours() % HOURS_PER_DAY,
                duration.toMinutes() % MINUTES_PER_HOUR,
                duration.getSeconds() % SECONDS_PER_MINUTE);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/MethodMeasurement.java
================================================
package com.github.games647.lagmonitor;

import com.google.common.collect.ImmutableMap;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.IntStream;

public class MethodMeasurement implements Comparable<MethodMeasurement> {

    private final String id;
    private final String className;
    private final String method;

    private final Map<String, MethodMeasurement> childInvokes = new HashMap<>();
    private long totalTime;

    public MethodMeasurement(String id, String className, String method) {
        this.id = id;

        this.className = className;
        this.method = method;
    }

    public String getId() {
        return id;
    }

    public String getClassName() {
        return className;
    }

    public String getMethod() {
        return method;
    }

    public long getTotalTime() {
        return totalTime;
    }

    public Map<String, MethodMeasurement> getChildInvokes() {
        return ImmutableMap.copyOf(childInvokes);
    }

    public float getTimePercent(long parentTime) {
        //one float conversion triggers the complete calculation to be decimal
        return ((float) totalTime / parentTime) * 100;
    }

    public void onMeasurement(StackTraceElement[] stackTrace, int skipElements, long time) {
        totalTime += time;

        if (skipElements >= stackTrace.length) {
            //we reached the end
            return;
        }

        StackTraceElement nextChildElement = stackTrace[stackTrace.length - skipElements - 1];
        String nextClass = nextChildElement.getClassName();
        String nextMethod = nextChildElement.getMethodName();

        String idName = nextChildElement.getClassName() + '.' + nextChildElement.getMethodName();
        MethodMeasurement child = childInvokes
                .computeIfAbsent(idName, (key) -> new MethodMeasurement(key, nextClass, nextMethod));
        child.onMeasurement(stackTrace, skipElements + 1, time);
    }

    public void writeString(StringBuilder builder, int indent) {
        StringBuilder b = new StringBuilder();
        IntStream.range(0, indent).forEach(i -> b.append(' '));

        String padding = b.toString();

        for (MethodMeasurement child : getChildInvokes().values()) {
            builder.append(padding).append(child.id).append("()");
            builder.append(' ');
            builder.append(child.totalTime).append("ms");
            builder.append('\n');
            child.writeString(builder, indent + 1);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MethodMeasurement that = (MethodMeasurement) o;

        return totalTime == that.totalTime &&
                Objects.equals(id, that.id) &&
                Objects.equals(className, that.className) &&
                Objects.equals(method, that.method) &&
                Objects.equals(childInvokes, that.childInvokes);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, className, method, childInvokes, totalTime);
    }

    @Override
    public int compareTo(MethodMeasurement other) {
        return Long.compare(this.totalTime, other.totalTime);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, MethodMeasurement> entry : getChildInvokes().entrySet()) {
            builder.append(entry.getKey()).append("()");
            builder.append(' ');
            builder.append(entry.getValue().totalTime).append("ms");
            builder.append('\n');
            entry.getValue().writeString(builder, 1);
        }

        return builder.toString();
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/NativeManager.java
================================================
package com.github.games647.lagmonitor;

import com.sun.management.UnixOperatingSystemMXBean;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

import oshi.SystemInfo;
import oshi.hardware.GlobalMemory;
import oshi.software.os.OSProcess;

public class NativeManager {

    private static final String JNA_FILE = "jna-5.5.0.jar";

    private final Logger logger;
    private final Path dataFolder;

    private final OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
    private SystemInfo info;

    private int pid = -1;

    public NativeManager(Logger logger, Path dataFolder) {
        this.logger = logger;
        this.dataFolder = dataFolder;
    }

    public void setupNativeAdapter() {
        logger.info("Found JNA native library. Enabling extended native data support to display more data");
        try {
            info = new SystemInfo();

            //make a test call
            pid = info.getOperatingSystem().getProcessId();
        } catch (UnsatisfiedLinkError | NoClassDefFoundError linkError) {
            logger.log(Level.INFO, "Cannot load native library. Continuing without it...", linkError);
            info = null;
        }
    }

    public Optional<SystemInfo> getSystemInfo() {
        return Optional.ofNullable(info);
    }

    public double getProcessCPULoad() {
        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;
            return nativeOsBean.getProcessCpuLoad();
        }

        return -1;
    }

    public Optional<OSProcess> getProcess() {
        if (info == null) {
            return Optional.empty();
        }

        return Optional.of(info.getOperatingSystem().getProcess(pid));
    }

    public double getCPULoad() {
        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;
            return nativeOsBean.getSystemCpuLoad();
        } else if (info != null) {
            return info.getHardware().getProcessor().getSystemLoadAverage(1)[0];
        }

        return -1;
    }

    public long getOpenFileDescriptors() {
        if (osBean instanceof com.sun.management.UnixOperatingSystemMXBean) {
            return ((UnixOperatingSystemMXBean) osBean).getOpenFileDescriptorCount();
        } else if (info != null) {
            return info.getOperatingSystem().getFileSystem().getOpenFileDescriptors();
        }

        return -1;
    }

    public long getMaxFileDescriptors() {
        if (osBean instanceof com.sun.management.UnixOperatingSystemMXBean) {
            return ((UnixOperatingSystemMXBean) osBean).getMaxFileDescriptorCount();
        } else if (info != null) {
            return info.getOperatingSystem().getFileSystem().getMaxFileDescriptors();
        }

        return -1;
    }

    public long getTotalMemory() {
        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;
            return nativeOsBean.getTotalPhysicalMemorySize();
        } else if (info != null) {
            return info.getHardware().getMemory().getTotal();
        }

        return -1;
    }

    public long getFreeMemory() {
        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;
            return nativeOsBean.getFreePhysicalMemorySize();
        } else if (info != null) {
            return getTotalMemory() - info.getHardware().getMemory().getAvailable();
        }

        return -1;
    }

    public long getFreeSwap() {
        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;
            return nativeOsBean.getFreeSwapSpaceSize();
        } else if (info != null) {
            GlobalMemory memory = info.getHardware().getMemory();
            return memory.getAvailable();
        }

        return -1;
    }

    public long getTotalSwap() {
        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;
            return nativeOsBean.getTotalSwapSpaceSize();
        } else if (info != null) {
            return info.getHardware().getMemory().getVirtualMemory().getSwapTotal();
        }

        return -1;
    }

    public long getFreeSpace() {
        long freeSpace = 0;
        try {
            FileStore fileStore = Files.getFileStore(Paths.get("."));
            freeSpace = fileStore.getUsableSpace();
        } catch (IOException ioEx) {
            logger.log(Level.WARNING, "Cannot calculate free/total disk space", ioEx);
        }

        return freeSpace;
    }

    public long getTotalSpace() {
        long totalSpace = 0;
        try {
            FileStore fileStore = Files.getFileStore(Paths.get("."));
            totalSpace = fileStore.getTotalSpace();
        } catch (IOException ioEx) {
            logger.log(Level.WARNING, "Cannot calculate free disk space", ioEx);
        }

        return totalSpace;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/Pages.java
================================================
package com.github.games647.lagmonitor;

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.List;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.ComponentBuilder.FormatRetention;
import net.md_5.bungee.api.chat.HoverEvent;

import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.util.ChatPaginator;

public class Pages {

    private static final int PAGINATION_LINES = 2;

    private static final int CONSOLE_HEIGHT = 40 - PAGINATION_LINES;
    private static final int PLAYER_HEIGHT = ChatPaginator.OPEN_CHAT_PAGE_HEIGHT - PAGINATION_LINES;

    public static String filterPackageNames(String packageName) {
        String text = packageName;
        if (text.contains("net.minecraft.server")) {
            text = text.replace("net.minecraft.server", "NMS");
        } else if (text.contains("org.bukkit.craftbukkit")) {
            text = text.replace("org.bukkit.craftbukkit", "OBC");
        }

        //IDEA: if it's a player we need to shorten the text more aggressively
        //maybe replacing the package with the plugin name
        //by getting the package name from the plugin.yml?
        return text;
    }

    private final String date = LocalTime.now().format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT));
    private final String title;

    private final List<BaseComponent[]> lines;

    private int lastSentPage = 1;

    public Pages(String title, List<BaseComponent[]> lines) {
        this.title = title;
        this.lines = lines;
    }

    public int getTotalPages(boolean isPlayer) {
        if (isPlayer) {
            return (lines.size() / PLAYER_HEIGHT) + 1;
        }

        return (lines.size() / CONSOLE_HEIGHT) + 1;
    }

    public List<BaseComponent[]> getAllLines() {
        return lines;
    }

    public int getLastSentPage() {
        return lastSentPage;
    }

    public void setLastSentPage(int lastSentPage) {
        this.lastSentPage = lastSentPage;
    }

    public List<BaseComponent[]> getPage(int page, boolean isPlayer) {
        int startIndex;
        int endIndex;
        if (isPlayer) {
            startIndex = (page - 1) * PLAYER_HEIGHT;
            endIndex = page * PLAYER_HEIGHT;
        } else {
            startIndex = (page - 1) * CONSOLE_HEIGHT;
            endIndex = page * CONSOLE_HEIGHT;
        }

        if (startIndex >= lines.size()) {
            endIndex = lines.size() - 1;
            startIndex = endIndex;
        } else if (endIndex >= lines.size()) {
            endIndex = lines.size() - 1;
        }

        return lines.subList(startIndex, endIndex);
    }

    public BaseComponent[] buildHeader(int page, int totalPages) {
        return new ComponentBuilder(title + " from " + date)
                .color(ChatColor.GOLD)
                .append(" << ")
                .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT
                        , new ComponentBuilder("Go to the previous page").create()))
                .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/lagpage " + (page - 1)))
                .color(ChatColor.DARK_AQUA)
                .append(page + " / " + totalPages, FormatRetention.NONE)
                .color(ChatColor.GRAY)
                .append(" >>")
                .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT
                        , new ComponentBuilder("Go to the next page").create()))
                .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/lagpage " + (page + 1)))
                .color(ChatColor.DARK_AQUA)
                .create();
    }

    public String buildFooter(int page, boolean isPlayer) {
        int endIndex;
        if (isPlayer) {
            endIndex = page * PLAYER_HEIGHT;
        } else {
            endIndex = page * CONSOLE_HEIGHT;
        }

        if (endIndex < lines.size()) {
            //Index starts by 0
            int remaining = lines.size() - endIndex - 1;
            return "... " + remaining + " more entries. Click the arrows above or type /lagpage next";
        }

        return "";
    }

    public void send(CommandSender sender) {
        send(sender, 1);
    }

    public void send(CommandSender sender, int page) {
        this.lastSentPage = page;

        if (sender instanceof Player) {
            Player player = (Player) sender;
            player.spigot().sendMessage(buildHeader(page, getTotalPages(true)));
            
            getPage(page, true).forEach(player.spigot()::sendMessage);

            String footer = buildFooter(page, true);
            if (!footer.isEmpty()) {
                sender.sendMessage(ChatColor.GOLD + footer);
            }
        } else {
            BaseComponent[] header = buildHeader(page, getTotalPages(false));
            StringBuilder headerBuilder = new StringBuilder();
            for (BaseComponent component : header) {
                headerBuilder.append(component.toLegacyText());
            }

            sender.sendMessage(headerBuilder.toString());
            getPage(page, false).stream().map(line -> {
                StringBuilder lineBuilder = new StringBuilder();
                for (BaseComponent component : line) {
                    lineBuilder.append(component.toLegacyText());
                }
                
                return lineBuilder.toString();
            }).forEach(sender::sendMessage);

            String footer = buildFooter(page, false);
            if (!footer.isEmpty()) {
                sender.sendMessage(ChatColor.GOLD + footer);
            }
        }
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/EnvironmentCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.NativeManager;

import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.text.DecimalFormat;
import java.util.Map.Entry;
import java.util.Optional;

import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.CentralProcessor.ProcessorIdentifier;
import oshi.software.os.OperatingSystem;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;

import static com.github.games647.lagmonitor.util.LagUtils.readableBytes;

public class EnvironmentCommand extends LagCommand {

    public EnvironmentCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();

        //os general info
        sendMessage(sender, "OS Name", osBean.getName());
        sendMessage(sender, "OS Arch", osBean.getArch());

        Optional<SystemInfo> optInfo = plugin.getNativeData().getSystemInfo();
        if (optInfo.isPresent()) {
            SystemInfo systemInfo = optInfo.get();

            OperatingSystem osInfo = systemInfo.getOperatingSystem();
            sendMessage(sender, "OS family", osInfo.getFamily());
            sendMessage(sender, "OS version", osInfo.getVersionInfo().toString());
            sendMessage(sender, "OS Manufacturer", osInfo.getManufacturer());

            sendMessage(sender, "Total processes", String.valueOf(osInfo.getProcessCount()));
            sendMessage(sender, "Total threads", String.valueOf(osInfo.getThreadCount()));
        }

        //CPU
        sender.sendMessage(PRIMARY_COLOR + "CPU:");
        if (optInfo.isPresent()) {
            CentralProcessor processor = optInfo.get().getHardware().getProcessor();
            ProcessorIdentifier identifier = processor.getProcessorIdentifier();

            sendMessage(sender, "    Vendor", identifier.getVendor());
            sendMessage(sender, "    Family", identifier.getFamily());
            sendMessage(sender, "    Name", identifier.getName());
            sendMessage(sender, "    Model", identifier.getModel());
            sendMessage(sender, "    Id", identifier.getIdentifier());
            sendMessage(sender, "    Vendor freq", String.valueOf(identifier.getVendorFreq()));
            sendMessage(sender, "    Physical Cores", String.valueOf(processor.getPhysicalProcessorCount()));
        }

        sendMessage(sender, "    Logical Cores", String.valueOf(osBean.getAvailableProcessors()));
        sendMessage(sender, "    Endian", System.getProperty("sun.cpu.endian", "Unknown"));

        sendMessage(sender, "Load Average", String.valueOf(osBean.getSystemLoadAverage()));
        printExtendOsInfo(sender);

        displayDiskSpace(sender);

        NativeManager nativeData = plugin.getNativeData();
        sendMessage(sender, "Open file descriptors", String.valueOf(nativeData.getOpenFileDescriptors()));
        sendMessage(sender, "Max file descriptors", String.valueOf(nativeData.getMaxFileDescriptors()));

        sender.sendMessage(PRIMARY_COLOR + "Variables:");
        for (Entry<String, String> variable : System.getenv().entrySet()) {
            sendMessage(sender, "    " + variable.getKey(), variable.getValue());
        }

        return true;
    }

    private void printExtendOsInfo(CommandSender sender) {
        NativeManager nativeData = plugin.getNativeData();

        //cpu
        double systemCpuLoad = nativeData.getCPULoad();
        double processCpuLoad = nativeData.getProcessCPULoad();

        //these numbers are in percent (0.01 -> 1%)
        //we want to to have four places in a human readable percent value to multiple it with 100
        DecimalFormat decimalFormat = new DecimalFormat("###.#### %");
        decimalFormat.setMultiplier(100);
        String systemLoadFormat = decimalFormat.format(systemCpuLoad);
        String processLoadFormat = decimalFormat.format(processCpuLoad);

        sendMessage(sender,"System Usage", systemLoadFormat);
        sendMessage(sender,"Process Usage", processLoadFormat);

        //swap
        long totalSwap = nativeData.getTotalSwap();
        long freeSwap = nativeData.getFreeSwap();
        sendMessage(sender, "Total Swap", readableBytes(totalSwap));
        sendMessage(sender, "Free Swap", readableBytes(freeSwap));

        //RAM
        long totalMemory = nativeData.getTotalMemory();
        long freeMemory = nativeData.getFreeMemory();
        sendMessage(sender, "Total OS RAM", readableBytes(totalMemory));
        sendMessage(sender, "Free OS RAM", readableBytes(freeMemory));
    }

    private void displayDiskSpace(CommandSender sender) {
        long freeSpace = plugin.getNativeData().getFreeSpace();
        long totalSpace = plugin.getNativeData().getTotalSpace();

        //Disk info
        sendMessage(sender,"Disk Size", readableBytes(totalSpace));
        sendMessage(sender,"Free Space", readableBytes(freeSpace));
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/GraphCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.graph.ClassesGraph;
import com.github.games647.lagmonitor.graph.CombinedGraph;
import com.github.games647.lagmonitor.graph.CpuGraph;
import com.github.games647.lagmonitor.graph.GraphRenderer;
import com.github.games647.lagmonitor.graph.HeapGraph;
import com.github.games647.lagmonitor.graph.ThreadsGraph;
import com.github.games647.lagmonitor.util.LagUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.MapMeta;
import org.bukkit.map.MapView;

import static java.util.stream.Collectors.toList;

public class GraphCommand extends LagCommand implements TabExecutor {

    private static final int MAX_COMBINED = 4;

    private final Map<String, GraphRenderer> graphTypes = new HashMap<>();

    public GraphCommand(LagMonitor plugin) {
        super(plugin);

        graphTypes.put("classes", new ClassesGraph());
        graphTypes.put("cpu", new CpuGraph(plugin, plugin.getNativeData()));
        graphTypes.put("heap", new HeapGraph());
        graphTypes.put("threads", new ThreadsGraph());
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        if (sender instanceof Player) {
            Player player = (Player) sender;

            if (args.length > 0) {
                if (args.length > 1) {
                    buildCombinedGraph(player, args);
                } else {
                    String graph = args[0];
                    GraphRenderer renderer = graphTypes.get(graph);
                    if (renderer == null) {
                        sendError(sender, "Unknown graph type");
                    } else {
                        giveMap(player, installRenderer(player, renderer));
                    }
                }

                return true;
            }

            //default is heap usage
            GraphRenderer graphRenderer = graphTypes.get("heap");
            MapView mapView = installRenderer(player, graphRenderer);
            giveMap(player, mapView);
        } else {
            sendError(sender, "Not implemented for the console");
        }

        return true;
    }

    @Override
    public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
        if (args.length != 1) {
            return Collections.emptyList();
        }

        String lastArg = args[args.length - 1];
        return graphTypes.keySet().stream()
                .filter(type -> type.startsWith(lastArg))
                .sorted(String.CASE_INSENSITIVE_ORDER)
                .collect(toList());
    }

    private void buildCombinedGraph(Player player, String[] args) {
        List<GraphRenderer> renderers = new ArrayList<>();
        for (String arg : args) {
            GraphRenderer renderer = graphTypes.get(arg);
            if (renderer == null) {
                sendError(player, "Unknown graph type " + arg);
                return;
            }

            renderers.add(renderer);
        }

        if (renderers.size() > MAX_COMBINED) {
            sendError(player, "Too many graphs");
        } else {
            CombinedGraph combinedGraph = new CombinedGraph(renderers.toArray(new GraphRenderer[0]));
            MapView view = installRenderer(player, combinedGraph);
            giveMap(player, view);
        }
    }

    private void giveMap(Player player, MapView mapView) {
        PlayerInventory inventory = player.getInventory();

        ItemStack mapItem;
        if (LagUtils.isFilledMapSupported()) {
            mapItem = new ItemStack(Material.FILLED_MAP, 1);
            ItemMeta meta = mapItem.getItemMeta();
            if (meta instanceof MapMeta) {
                MapMeta mapMeta = (MapMeta) meta;
                mapMeta.setMapView(mapView);
                mapItem.setItemMeta(meta);
            }
        } else {
            mapItem = new ItemStack(Material.MAP, 1, (short) mapView.getId());
        }

        inventory.addItem(mapItem);
        player.sendMessage(ChatColor.DARK_GREEN + "You received a map with the graph");
    }

    private MapView installRenderer(Player player, GraphRenderer graphType) {
        MapView mapView = Bukkit.createMap(player.getWorld());
        mapView.getRenderers().forEach(mapView::removeRenderer);

        mapView.addRenderer(graphType);
        return mapView;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/HelpCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;

import java.util.Map;
import java.util.Map.Entry;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.HoverEvent.Action;
import net.md_5.bungee.api.chat.TextComponent;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.util.ChatPaginator;

public class HelpCommand extends LagCommand {

    private static final int HOVER_MAX_LENGTH = 40;

    public HelpCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        sender.sendMessage(ChatColor.AQUA + plugin.getName() + "-Help");

        int maxWidth = ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH;
        if (!(sender instanceof Player)) {
            maxWidth = Integer.MAX_VALUE;
        }

        for (Entry<String, Map<String, Object>> entry : plugin.getDescription().getCommands().entrySet()) {
            String commandKey = entry.getKey();
            Map<String, Object> value = entry.getValue();

            String description = ' ' + value.getOrDefault("description", "No description").toString();
            String usage = ((String) value.getOrDefault("usage", '/' + commandKey)).replace("<command>", commandKey);

            TextComponent component = createCommandHelp(usage, description, maxWidth);
            LagCommand.send(sender, component);
        }

        return true;
    }

    private TextComponent createCommandHelp(String usage, String description, int maxWidth) {
        TextComponent usageComponent = new TextComponent(usage);
        usageComponent.setColor(ChatColor.DARK_AQUA);

        TextComponent descriptionComponent = new TextComponent(description);
        descriptionComponent.setColor(ChatColor.GOLD);
        int totalLen = usage.length() + description.length();
        if (totalLen > maxWidth) {
            int newDescLength = maxWidth - usage.length() - 3 - 1;
            if (newDescLength < 0) {
                newDescLength = 0;
            }

            String shortDesc = description.substring(0, newDescLength) + "...";
            descriptionComponent.setText(shortDesc);

            ComponentBuilder hoverBuilder = new ComponentBuilder("");

            String[] separated = ChatPaginator.wordWrap(description, HOVER_MAX_LENGTH);
            for (String line : separated) {
                hoverBuilder.append(line + '\n');
                hoverBuilder.color(ChatColor.GOLD);
            }

            descriptionComponent.setHoverEvent(new HoverEvent(Action.SHOW_TEXT, hoverBuilder.create()));
        } else {
            descriptionComponent.setText(description);
        }

        usageComponent.addExtra(descriptionComponent);
        return usageComponent;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/LagCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;

import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;

public abstract class LagCommand implements CommandExecutor {

    protected static final ChatColor PRIMARY_COLOR = ChatColor.DARK_AQUA;
    protected static final ChatColor SECONDARY_COLOR = ChatColor.GRAY;

    protected static final String NATIVE_NOT_FOUND = "Native library not found. Please download it and place it " +
            "inside configuration folder of this plugin to see this data";

    protected final LagMonitor plugin;

    public LagCommand(LagMonitor plugin) {
        this.plugin = plugin;
    }

    private boolean isCommandAllowed(Command cmd, CommandSender sender) {
        if (!(sender instanceof Player)) {
            return true;
        }

        FileConfiguration config = plugin.getConfig();

        Collection<String> aliases = new ArrayList<>(cmd.getAliases());
        aliases.add(cmd.getName());
        for (String alias : aliases) {
            List<String> aliasAllowed = config.getStringList("allow-" + alias);
            if (!aliasAllowed.isEmpty()) {
                return aliasAllowed.contains(sender.getName());
            }
        }

        // allowlist doesn't exist
        return true;
    }

    public boolean canExecute(CommandSender sender, Command cmd) {
        if (!isCommandAllowed(cmd, sender)) {
            sendError(sender, "Command not allowed for you!");
            return false;
        }

        return true;
    }

    protected void sendMessage(CommandSender sender, String title, String value) {
        sender.sendMessage(PRIMARY_COLOR + title + ": " + SECONDARY_COLOR + value);
    }

    protected void sendError(CommandSender sender, String msg) {
        sender.sendMessage(ChatColor.DARK_RED + msg);
    }

    public static void send(CommandSender sender, BaseComponent... components) {
        //CommandSender#sendMessage(BaseComponent[]) was introduced after 1.8. This is a backwards compatible solution
        if (sender instanceof Player) {
            sender.spigot().sendMessage(components);
        } else {
            sender.sendMessage(TextComponent.toLegacyText(components));
        }
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/MbeanCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;

import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Stream;

import javax.management.MBeanAttributeInfo;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;

import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;

import static java.util.stream.Collectors.toList;

public class MbeanCommand extends LagCommand implements TabExecutor {

    public MbeanCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();

        if (args.length > 0) {
            try {
                ObjectName beanObject = ObjectName.getInstance(args[0]);
                if (args.length > 1) {
                    Object result = mBeanServer.getAttribute(beanObject, args[1]);
                    sender.sendMessage(ChatColor.DARK_GREEN + Objects.toString(result));
                } else {
                    MBeanAttributeInfo[] attributes = mBeanServer.getMBeanInfo(beanObject).getAttributes();
                    for (MBeanAttributeInfo attribute : attributes) {
                        if ("ObjectName".equals(attribute.getName())) {
                            //ignore the object name - it's already known if the user invoke the command
                            continue;
                        }

                        sender.sendMessage(ChatColor.YELLOW + attribute.getName());
                    }
                }
            } catch (Exception ex) {
                plugin.getLogger().log(Level.SEVERE, null, ex);
            }
        } else {
            Set<ObjectInstance> allBeans = mBeanServer.queryMBeans(null, null);
            allBeans.stream()
                    .map(ObjectInstance::getObjectName)
                    .map(ObjectName::getCanonicalName)
                    .forEach(bean -> sender.sendMessage(ChatColor.DARK_AQUA + bean));
        }

        return true;
    }

    @Override
    public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
        String lastArg = args[args.length - 1];

        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();

        Stream<String> result = Stream.empty();
        if (args.length == 1) {
            Set<ObjectName> mbeans = mBeanServer.queryNames(null, null);
            result = mbeans.stream()
                    .map(ObjectName::getCanonicalName)
                    .filter(name -> name.startsWith(lastArg));
        } else if (args.length == 2) {
            try {
                ObjectName beanObject = ObjectName.getInstance(args[0]);
                result = Arrays.stream(mBeanServer.getMBeanInfo(beanObject).getAttributes())
                        .map(MBeanAttributeInfo::getName)
                        //ignore the object name - it's already known if the user invoke the command
                        .filter(attribute -> !"ObjectName".equals(attribute));
            } catch (Exception ex) {
                plugin.getLogger().log(Level.SEVERE, null, ex);
            }
        }

        return result.sorted(String.CASE_INSENSITIVE_ORDER).collect(toList());
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/MonitorCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.MethodMeasurement;
import com.github.games647.lagmonitor.Pages;
import com.github.games647.lagmonitor.task.MonitorTask;
import com.google.common.base.Strings;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Timer;
import java.util.concurrent.TimeUnit;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ClickEvent.Action;
import net.md_5.bungee.api.chat.ComponentBuilder;

import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;

public class MonitorCommand extends LagCommand {

    public static final long SAMPLE_INTERVAL = 100L;
    public static final long SAMPLE_DELAY = TimeUnit.SECONDS.toMillis(1)  / 2;

    private MonitorTask monitorTask;

    public MonitorCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        if (args.length > 0) {
            String monitorCommand = args[0].toLowerCase();
            switch (monitorCommand) {
                case "start":
                    startMonitor(sender);
                    break;
                case "stop":
                    stopMonitor(sender);
                    break;
                case "paste":
                    pasteMonitor(sender);
                    break;
                default:
                    sendError(sender, "Invalid command parameter");
            }
        } else if (monitorTask == null) {
            sendError(sender, "Monitor is not running");
        } else {
            List<BaseComponent[]> lines = new ArrayList<>();
            synchronized (this) {
                MethodMeasurement rootSample = monitorTask.getRootSample();
                printTrace(lines, 0, rootSample, 0);
            }

            Pages pagination = new Pages("Monitor", lines);
            pagination.send(sender);
            this.plugin.getPageManager().setPagination(sender.getName(), pagination);
        }

        return true;
    }

    private void printTrace(List<BaseComponent[]> lines, long parentTime, MethodMeasurement current, int depth) {
        String space = Strings.repeat(" ", depth);

        long currentTime = current.getTotalTime();
        float timePercent = current.getTimePercent(parentTime);

        String clazz = Pages.filterPackageNames(current.getClassName());
        String method = current.getMethod();
        lines.add(new ComponentBuilder(space + "[-] ")
                .append(clazz + '.')
                .color(ChatColor.DARK_AQUA)
                .append(method)
                .color(ChatColor.DARK_GREEN)
                .append(' ' + timePercent + "%")
                .color(ChatColor.GRAY)
                .create());

        Collection<MethodMeasurement> childInvokes = current.getChildInvokes().values();
        List<MethodMeasurement> sortedList = new ArrayList<>(childInvokes);
        Collections.sort(sortedList);

        sortedList.forEach((child) -> printTrace(lines, currentTime, child, depth + 1));
    }

    private void startMonitor(CommandSender sender) {
        Timer timer = plugin.getMonitorTimer();
        if (monitorTask == null && timer == null) {
            timer = new Timer(plugin.getName() + "-Monitor");
            plugin.setMonitorTimer(timer);

            monitorTask = new MonitorTask(plugin.getLogger(), Thread.currentThread().getId());
            timer.scheduleAtFixedRate(monitorTask, SAMPLE_DELAY, SAMPLE_INTERVAL);

            sender.sendMessage(ChatColor.DARK_GREEN + "Monitor started");
        } else {
            sendError(sender, "Monitor task is already running");
        }
    }

    private void stopMonitor(CommandSender sender) {
        Timer timer = plugin.getMonitorTimer();
        if (monitorTask == null && timer == null) {
            sendError(sender, "Monitor is not running");
        } else {
            monitorTask = null;
            if (timer != null) {
                timer.cancel();
                timer.purge();
                plugin.setMonitorTimer(null);
            }

            sender.sendMessage(ChatColor.DARK_GREEN + "Monitor stopped");
        }
    }

    private void pasteMonitor(final CommandSender sender) {
        Timer timer = plugin.getMonitorTimer();
        if (monitorTask == null && timer == null) {
            sendError(sender, "Monitor is not running");
        }

        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            String reportUrl = monitorTask.paste();
            if (reportUrl == null) {
                sendError(sender, "Error occurred. Please check the console");
            } else {
                String profileUrl = reportUrl + ".profile";
                send(sender, new ComponentBuilder("Report url: " + profileUrl)
                        .color(ChatColor.GREEN)
                        .event(new ClickEvent(Action.OPEN_URL, profileUrl))
                        .create());
            }
        });
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/NativeCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.util.LagUtils;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import oshi.SystemInfo;
import oshi.demo.DetectVM;
import oshi.hardware.Baseboard;
import oshi.hardware.ComputerSystem;
import oshi.hardware.Firmware;
import oshi.hardware.HWDiskStore;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.hardware.PhysicalMemory;
import oshi.hardware.Sensors;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;

public class NativeCommand extends LagCommand {

    public NativeCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        Optional<SystemInfo> optInfo = plugin.getNativeData().getSystemInfo();
        if (optInfo.isPresent()) {
            displayNativeInfo(sender, optInfo.get());
        } else {
            sendError(sender, NATIVE_NOT_FOUND);
        }

        return true;
    }

    private void displayNativeInfo(CommandSender sender, SystemInfo systemInfo) {
        HardwareAbstractionLayer hardware = systemInfo.getHardware();
        OperatingSystem operatingSystem = systemInfo.getOperatingSystem();

        //swap and load is already available in the environment command because MBeans already supports this
        long uptime = TimeUnit.SECONDS.toMillis(operatingSystem.getSystemUptime());
        String uptimeFormat = LagMonitor.formatDuration(Duration.ofMillis(uptime));
        sendMessage(sender, "OS Uptime", uptimeFormat);

        String startTime = LagMonitor.formatDuration(Duration.ofMillis(uptime));
        sendMessage(sender, "OS Start time", startTime);

        sendMessage(sender, "CPU Freq", Arrays.toString(hardware.getProcessor().getCurrentFreq()));
        sendMessage(sender, "CPU Max Freq", String.valueOf(hardware.getProcessor().getMaxFreq()));
        sendMessage(sender, "VM Hypervisor", DetectVM.identifyVM());

        //disk
        printDiskInfo(sender, hardware.getDiskStores());
        displayMounts(sender, operatingSystem.getFileSystem().getFileStores());

        printSensorsInfo(sender, hardware.getSensors());
        printBoardInfo(sender, hardware.getComputerSystem());

        printRAMInfo(sender, hardware.getMemory().getPhysicalMemory());
    }

    private void printRAMInfo(CommandSender sender, List<PhysicalMemory> physicalMemories) {
        sender.sendMessage(PRIMARY_COLOR + "Memory:");
        for (PhysicalMemory memory : physicalMemories) {
            sendMessage(sender, "    Label", memory.getBankLabel());
            sendMessage(sender, "    Manufacturer", memory.getManufacturer());
            sendMessage(sender, "    Type", memory.getMemoryType());
            sendMessage(sender, "    Capacity", LagUtils.readableBytes(memory.getCapacity()));
            sendMessage(sender, "    Clock speed", String.valueOf(memory.getClockSpeed()));
        }
    }

    private void printBoardInfo(CommandSender sender, ComputerSystem computerSystem) {
        sendMessage(sender, "System Manufacturer", computerSystem.getManufacturer());
        sendMessage(sender, "System model", computerSystem.getModel());
        sendMessage(sender, "Serial number", computerSystem.getSerialNumber());

        sender.sendMessage(PRIMARY_COLOR + "Baseboard:");
        Baseboard baseboard = computerSystem.getBaseboard();
        sendMessage(sender, "    Manufacturer", baseboard.getManufacturer());
        sendMessage(sender, "    Model", baseboard.getModel());
        sendMessage(sender, "    Serial", baseboard.getVersion());
        sendMessage(sender, "    Version", baseboard.getVersion());

        sender.sendMessage(PRIMARY_COLOR + "BIOS Firmware:");
        Firmware firmware = computerSystem.getFirmware();
        sendMessage(sender, "    Manufacturer", firmware.getManufacturer());
        sendMessage(sender, "    Name", firmware.getName());
        sendMessage(sender, "    Description", firmware.getDescription());
        sendMessage(sender, "    Version", firmware.getVersion());

        sendMessage(sender, "    Release date", firmware.getReleaseDate());
    }

    private void printSensorsInfo(CommandSender sender, Sensors sensors) {
        double cpuTemperature = sensors.getCpuTemperature();
        sendMessage(sender, "CPU Temp °C", String.valueOf(LagUtils.round(cpuTemperature)));
        sendMessage(sender, "Voltage", String.valueOf(LagUtils.round(sensors.getCpuVoltage())));

        int[] fanSpeeds = sensors.getFanSpeeds();
        sendMessage(sender, "Fan speed (rpm)", Arrays.toString(fanSpeeds));
    }

    private void printDiskInfo(CommandSender sender, List<HWDiskStore> diskStores) {
        //disk read write
        long diskReads = diskStores.stream().mapToLong(HWDiskStore::getReadBytes).sum();
        long diskWrites = diskStores.stream().mapToLong(HWDiskStore::getWriteBytes).sum();

        sendMessage(sender, "Disk read bytes", LagUtils.readableBytes(diskReads));
        sendMessage(sender, "Disk write bytes", LagUtils.readableBytes(diskWrites));

        sender.sendMessage(PRIMARY_COLOR + "Disks:");
        for (HWDiskStore disk : diskStores) {
            String size = LagUtils.readableBytes(disk.getSize());
            sendMessage(sender, "    " + disk.getName(), disk.getModel() + ' ' + size);
        }
    }

    private void displayMounts(CommandSender sender, List<OSFileStore> fileStores) {
        sender.sendMessage(PRIMARY_COLOR + "Mounts:");
        for (OSFileStore fileStore : fileStores) {
            printMountInfo(sender, fileStore);
        }
    }

    private void printMountInfo(CommandSender sender, OSFileStore fileStore) {
        String type = fileStore.getType();
        String desc = fileStore.getDescription();

        long totalSpaceBytes = fileStore.getTotalSpace();
        String totalSpace = LagUtils.readableBytes(totalSpaceBytes);
        String usedSpace = LagUtils.readableBytes(totalSpaceBytes - fileStore.getUsableSpace());

        String format = desc + ' ' + type + ' ' + usedSpace + '/' + totalSpace;
        sendMessage(sender, "    " + fileStore.getMount(), format);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/NetworkCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.util.LagUtils;

import java.util.Arrays;
import java.util.Optional;

import oshi.SystemInfo;
import oshi.hardware.NetworkIF;
import oshi.software.os.NetworkParams;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;

public class NetworkCommand extends LagCommand {

    public NetworkCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        Optional<SystemInfo> optInfo = plugin.getNativeData().getSystemInfo();
        if (optInfo.isPresent()) {
            displayNetworkInfo(sender, optInfo.get());
        } else {
            sender.sendMessage(NATIVE_NOT_FOUND);
        }

        return true;
    }

    private void displayNetworkInfo(CommandSender sender, SystemInfo systemInfo) {
        displayGlobalNetworkInfo(sender, systemInfo.getOperatingSystem().getNetworkParams());
        for (NetworkIF networkInterface : systemInfo.getHardware().getNetworkIFs()) {
            displayInterfaceInfo(sender, networkInterface);
        }
    }

    private void displayGlobalNetworkInfo(CommandSender sender, NetworkParams networkParams) {
        sendMessage(sender, "Domain name", networkParams.getDomainName());
        sendMessage(sender, "Host name", networkParams.getHostName());
        sendMessage(sender, "Default IPv4 Gateway", networkParams.getIpv4DefaultGateway());
        sendMessage(sender, "Default IPv6 Gateway", networkParams.getIpv6DefaultGateway());
        sendMessage(sender, "DNS servers", Arrays.toString(networkParams.getDnsServers()));
    }

    private void displayInterfaceInfo(CommandSender sender, NetworkIF networkInterface) {
        sendMessage(sender, "Name", networkInterface.getName());
        sendMessage(sender, "    Display", networkInterface.getDisplayName());
        sendMessage(sender, "    MAC", networkInterface.getMacaddr());
        sendMessage(sender, "    MTU", String.valueOf(networkInterface.getMTU()));
        sendMessage(sender, "    IPv4", Arrays.toString(networkInterface.getIPv4addr()));
        sendMessage(sender, "    IPv6", Arrays.toString(networkInterface.getIPv6addr()));
        sendMessage(sender, "    Speed", String.valueOf(networkInterface.getSpeed()));

        String receivedBytes = LagUtils.readableBytes(networkInterface.getBytesRecv());
        String sentBytes = LagUtils.readableBytes(networkInterface.getBytesSent());
        sendMessage(sender, "    Received", receivedBytes);
        sendMessage(sender, "    Sent", sentBytes);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/PaginationCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.Pages;
import com.github.games647.lagmonitor.command.dump.DumpCommand;
import com.google.common.primitives.Ints;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.logging.Level;

import net.md_5.bungee.api.chat.BaseComponent;

import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

public class PaginationCommand extends DumpCommand {

    public PaginationCommand(LagMonitor plugin) {
        super(plugin, "pagination", "txt");
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        Pages pagination = plugin.getPageManager().getPagination(sender.getName());
        if (pagination == null) {
            sendError(sender, "You have no pagination session");
            return true;
        }

        if (args.length > 0) {
            String subCommand = args[0].toLowerCase();
            switch (subCommand) {
                case "next":
                    onNextPage(pagination, sender);
                    break;
                case "prev":
                    onPrevPage(pagination, sender);
                    break;
                case "all":
                    onShowAll(pagination, sender);
                    break;
                case "save":
                    onSave(pagination, sender);
                    break;
                default:
                    onPageNumber(subCommand, sender, pagination);
            }
        } else {
            sendError(sender, "Not enough arguments");
        }

        return true;
    }

    private void onPageNumber(String subCommand, CommandSender sender, Pages pagination) {
        Integer page = Ints.tryParse(subCommand);
        if (page == null) {
            sendError(sender, "Unknown subcommand or not a valid page number");
        } else {
            if (page < 1) {
                sendError(sender, "Page number too small");
                return;
            } else if (page > pagination.getTotalPages(sender instanceof Player)) {
                sendError(sender, "Page number too high");
                return;
            }

            pagination.send(sender, page);
        }
    }

    private void onNextPage(Pages pagination, CommandSender sender) {
        int lastPage = pagination.getLastSentPage();
        if (lastPage >= pagination.getTotalPages(sender instanceof Player)) {
            sendError(sender,"You are already on the last page");
            return;
        }

        pagination.send(sender, lastPage + 1);
    }

    private void onPrevPage(Pages pagination, CommandSender sender) {
        int lastPage = pagination.getLastSentPage();
        if (lastPage <= 1) {
            sendError(sender,"You are already on the first page");
            return;
        }

        pagination.send(sender, lastPage - 1);
    }

    private void onSave(Pages pagination, CommandSender sender) {
        StringBuilder lineBuilder = new StringBuilder();
        for (BaseComponent[] line : pagination.getAllLines()) {
            for (BaseComponent component : line) {
                lineBuilder.append(component.toLegacyText());
            }

            lineBuilder.append('\n');
        }

        Path dumpFile = getNewDumpFile();
        try {
            Files.write(dumpFile, Collections.singletonList(lineBuilder.toString()));
            sender.sendMessage(ChatColor.GRAY + "Dump created: " + dumpFile.getFileName());
        } catch (IOException ex) {
            plugin.getLogger().log(Level.SEVERE, null, ex);
        }
    }

    private void onShowAll(Pages pagination, CommandSender sender) {
        if (sender instanceof Player) {
            Player player = (Player) sender;
            player.spigot().sendMessage(pagination.buildHeader(1, 1));
        } else {
            BaseComponent[] header = pagination.buildHeader(1, 1);
            StringBuilder headerBuilder = new StringBuilder();
            for (BaseComponent component : header) {
                headerBuilder.append(component.toLegacyText());
            }

            sender.sendMessage(headerBuilder.toString());
        }

        pagination.getAllLines().stream().map((line) -> {
            StringBuilder lineBuilder = new StringBuilder();
            for (BaseComponent component : line) {
                lineBuilder.append(component.toLegacyText());
            }

            return lineBuilder.toString();
        }).forEach(sender::sendMessage);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/StackTraceCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.Pages;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;

public class StackTraceCommand extends LagCommand implements TabExecutor {

    private static final int MAX_DEPTH = 75;

    public StackTraceCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        if (args.length > 0) {
            String threadName = args[0];

            Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
            for (Map.Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet()) {
                Thread thread = entry.getKey();
                if (thread.getName().equalsIgnoreCase(threadName)) {
                    StackTraceElement[] stackTrace = entry.getValue();
                    printStackTrace(sender, stackTrace);
                    return true;
                }
            }

            sendError(sender, "No thread with that name found");
        } else {
            ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
            ThreadInfo threadInfo = threadBean.getThreadInfo(Thread.currentThread().getId(), MAX_DEPTH);
            printStackTrace(sender, threadInfo.getStackTrace());
        }

        return true;
    }

    private void printStackTrace(CommandSender sender, StackTraceElement[] stackTrace) {
        List<BaseComponent[]> lines = new ArrayList<>();

        //begin from the top
        for (int i = stackTrace.length - 1; i >= 0; i--) {
            lines.add(formatTraceElement(stackTrace[i]));
        }

        Pages pagination = new Pages("Stacktrace", lines);
        pagination.send(sender);
        plugin.getPageManager().setPagination(sender.getName(), pagination);
    }

    private BaseComponent[] formatTraceElement(StackTraceElement traceElement) {
        String className = Pages.filterPackageNames(traceElement.getClassName());
        String methodName = traceElement.getMethodName();

        boolean nativeMethod = traceElement.isNativeMethod();
        int lineNumber = traceElement.getLineNumber();

        String line = Integer.toString(lineNumber);
        if (nativeMethod) {
            line = "Native";
        }

        return new ComponentBuilder(className + '.')
                .color(PRIMARY_COLOR.asBungee())
                .append(methodName + ':')
                .color(ChatColor.DARK_GREEN)
                .append(line)
                .color(ChatColor.DARK_PURPLE)
                .create();
    }

    @Override
    public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
        List<String> result = new ArrayList<>();

        StringBuilder builder = new StringBuilder();
        for (String arg : args) {
            builder.append(arg).append(' ');
        }

        String requestName = builder.toString();

        ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(false, false);
        return Arrays.stream(threads)
                .map(ThreadInfo::getThreadName)
                .filter(name -> name.startsWith(requestName))
                .sorted(String.CASE_INSENSITIVE_ORDER)
                .collect(Collectors.toList());
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/VmCommand.java
================================================
package com.github.games647.lagmonitor.command;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.util.JavaVersion;

import java.lang.management.ClassLoadingMXBean;
import java.lang.management.CompilationMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.HoverEvent.Action;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;

public class VmCommand extends LagCommand {

    public VmCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        //java version info
        displayJavaVersion(sender);

        //java paths
        sendMessage(sender, "Java lib", System.getProperty("sun.boot.library.path", "Unknown"));
        sendMessage(sender, "Java home", System.getProperty("java.home", "Unknown"));
        sendMessage(sender, "Temp path", System.getProperty("java.io.tmpdir", "Unknown"));

        displayRuntimeInfo(sender, ManagementFactory.getRuntimeMXBean());
        displayCompilationInfo(sender, ManagementFactory.getCompilationMXBean());
        displayClassLoading(sender, ManagementFactory.getClassLoadingMXBean());

        //garbage collector
        for (GarbageCollectorMXBean collector : ManagementFactory.getGarbageCollectorMXBeans()) {
            displayCollectorStats(sender, collector);
        }

        return true;
    }

    private void displayCompilationInfo(CommandSender sender, CompilationMXBean compilationBean) {
        sendMessage(sender, "Compiler name", compilationBean.getName());
        sendMessage(sender, "Compilation time (ms)", String.valueOf(compilationBean.getTotalCompilationTime()));
    }

    private void displayRuntimeInfo(CommandSender sender, RuntimeMXBean runtimeBean) {
        //vm
        sendMessage(sender, "Java VM", runtimeBean.getVmName() + ' ' + runtimeBean.getVmVersion());
        sendMessage(sender, "Java vendor", runtimeBean.getVmVendor());

        //vm specification
        sendMessage(sender, "Spec name", runtimeBean.getSpecName());
        sendMessage(sender, "Spec vendor", runtimeBean.getSpecVendor());
        sendMessage(sender, "Spec version", runtimeBean.getSpecVersion());
    }

    private void displayCollectorStats(CommandSender sender, GarbageCollectorMXBean collector) {
        sendMessage(sender, "Garbage collector", collector.getName());
        sendMessage(sender, "    Count", String.valueOf(collector.getCollectionCount()));
        sendMessage(sender, "    Time (ms)", String.valueOf(collector.getCollectionTime()));
    }

    private void displayClassLoading(CommandSender sender, ClassLoadingMXBean classBean) {
        sendMessage(sender, "Loaded classes", String.valueOf(classBean.getLoadedClassCount()));
        sendMessage(sender, "Total loaded", String.valueOf(classBean.getTotalLoadedClassCount()));
        sendMessage(sender, "Unloaded classes", String.valueOf(classBean.getUnloadedClassCount()));
    }

    private void displayJavaVersion(CommandSender sender) {
        JavaVersion currentVersion = JavaVersion.detect();
        LagCommand.send(sender, formatJavaVersion(currentVersion));

        sendMessage(sender, "Java release date", System.getProperty("java.version.date", "n/a"));
        sendMessage(sender, "Class version", System.getProperty("java.class.version"));
    }

    private BaseComponent[] formatJavaVersion(JavaVersion version) {
        ComponentBuilder builder = new ComponentBuilder("Java version: ").color(PRIMARY_COLOR.asBungee())
                .append(version.getRaw()).color(SECONDARY_COLOR.asBungee());
        if (version.isOutdated()) {
            builder = builder.append(" (").color(ChatColor.WHITE)
                    .append("Outdated").color(ChatColor.DARK_RED)
                    .event(new HoverEvent(Action.SHOW_TEXT,
                            new ComponentBuilder("You're running an outdated Java version. \n"
                                    + "Java 9 and 10 are already released. \n"
                                    + "Newer versions could improve the performance or include bug or security fixes.")
                                    .color(ChatColor.DARK_AQUA).create()))
                    .append(")").color(ChatColor.WHITE);
        }

        return builder.create();
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/dump/DumpCommand.java
================================================
package com.github.games647.lagmonitor.command.dump;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.command.LagCommand;

import java.lang.management.ManagementFactory;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;

public abstract class DumpCommand extends LagCommand {

    //https://docs.oracle.com/javase/10/docs/jre/api/management/extension/com/sun/management/DiagnosticCommandMBean.html
    protected static final String DIAGNOSTIC_BEAN = "com.sun.management:type=DiagnosticCommand";
    protected static final String NOT_ORACLE_MSG = "You are not using Oracle JVM. OpenJDK hasn't implemented it yet";

    private final String filePrefix;
    private final String fileExt;

    private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");

    public DumpCommand(LagMonitor plugin, String filePrefix, String fileExt) {
        super(plugin);

        this.filePrefix = filePrefix;
        this.fileExt = '.' + fileExt;
    }

    public Path getNewDumpFile() {
        String timeSuffix = '-' + LocalDateTime.now().format(dateFormat);
        Path folder = plugin.getDataFolder().toPath();
        return folder.resolve(filePrefix + '-' + timeSuffix + fileExt);
    }

    public Object invokeBeanCommand(String beanName, String command, Object[] args, String[] signature)
            throws MalformedObjectNameException, MBeanException, InstanceNotFoundException, ReflectionException {
        MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
        ObjectName beanObject = ObjectName.getInstance(beanName);

        return beanServer.invoke(beanObject, command, args, signature);
    }

    public String invokeDiagnosticCommand(String command, String... args)
            throws MalformedObjectNameException, ReflectionException, MBeanException, InstanceNotFoundException {
        return (String) invokeBeanCommand(DIAGNOSTIC_BEAN, command,
                new Object[]{args}, new String[]{String[].class.getName()});
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/dump/FlightCommand.java
================================================
package com.github.games647.lagmonitor.command.dump;

import com.github.games647.lagmonitor.LagMonitor;

import java.lang.management.ManagementFactory;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.logging.Level;

import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanException;
import javax.management.MBeanFeatureInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;

public class FlightCommand extends DumpCommand {

    private static final String START_COMMAND = "jfrStart";
    private static final String STOP_COMMAND = "jfrStop";
    private static final String DUMP_COMMAND = "jfrDump";

    private static final String SETTINGS_FILE = "default.jfc";

    private final String settingsPath;
    private final String recordingName;

    private final boolean isSupported;

    public FlightCommand(LagMonitor plugin) {
        super(plugin, "flight_recorder", "jfr");

        this.recordingName = plugin.getName() + "-Record";
        this.settingsPath = plugin.getDataFolder().toPath().resolve(SETTINGS_FILE).toAbsolutePath().toString();

        isSupported = areFlightMethodsAvailable();
    }

    private boolean areFlightMethodsAvailable() {
        MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
        try {
            ObjectName objectName = ObjectName.getInstance(DIAGNOSTIC_BEAN);
            MBeanInfo beanInfo = beanServer.getMBeanInfo(objectName);
            return Arrays.stream(beanInfo.getOperations())
                    .map(MBeanFeatureInfo::getName)
                    .anyMatch(op -> op.contains("jfr"));
        } catch (JMException instanceNotFoundEx) {
            return false;
        }
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        if (!isSupported) {
            sendError(sender, NOT_ORACLE_MSG);
            return true;
        }

        try {
            if (args.length > 0) {
                String subCommand = args[0].toLowerCase();
                switch (subCommand) {
                    case "start":
                        onStartCommand(sender);
                        break;
                    case "stop":
                        onStopCommand(sender);
                        break;
                    case "dump":
                        onDumpCommand(sender);
                        break;
                    default:
                        sendError(sender, "Unknown subcommand");
                }
            } else {
                sendError(sender, "Not enough arguments");
            }
        } catch (InstanceNotFoundException notFoundEx) {
            sendError(sender, NOT_ORACLE_MSG);
        } catch (Exception ex) {
            plugin.getLogger().log(Level.SEVERE, null, ex);
            sendError(sender, "An exception occurred. Please check the server log");
        }

        return true;
    }

    private void onStartCommand(CommandSender sender)
            throws MalformedObjectNameException, ReflectionException, MBeanException, InstanceNotFoundException {
        String reply = invokeDiagnosticCommand(START_COMMAND, "settings=" + settingsPath, "name=" + recordingName);
        sender.sendMessage(reply);
    }

    private void onStopCommand(CommandSender sender)
            throws MalformedObjectNameException, ReflectionException, MBeanException, InstanceNotFoundException {
        String reply = invokeDiagnosticCommand(STOP_COMMAND, "name=" + recordingName);
        sender.sendMessage(reply);
    }

    private void onDumpCommand(CommandSender sender)
            throws MalformedObjectNameException, ReflectionException, MBeanException, InstanceNotFoundException {
        Path dumpFile = getNewDumpFile();
        String reply = invokeDiagnosticCommand(DUMP_COMMAND
                , "filename=" + dumpFile.toAbsolutePath(), "name=" + recordingName);

        sender.sendMessage(reply);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/dump/HeapCommand.java
================================================
package com.github.games647.lagmonitor.command.dump;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.Pages;
import com.sun.management.HotSpotDiagnosticMXBean;

import java.lang.management.ManagementFactory;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

import javax.management.InstanceNotFoundException;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;

public class HeapCommand extends DumpCommand {

    private static final String HEAP_COMMAND = "gcClassHistogram";
    private static final boolean DUMP_DEAD_OBJECTS = false;

    private static final String[] EMPTY_STRING = {};

    public HeapCommand(LagMonitor plugin) {
        super(plugin, "heap", "hprof");
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        if (args.length > 0) {
            String subCommand = args[0];
            if ("dump".equalsIgnoreCase(subCommand)) {
                onDump(sender);
            } else {
                sendError(sender, "Unknown subcommand");
            }

            return true;
        }

        List<BaseComponent[]> paginatedLines = new ArrayList<>();
        try {
            String reply = invokeDiagnosticCommand(HEAP_COMMAND, EMPTY_STRING);
            for (String line : reply.split("\n")) {
                paginatedLines.add(new ComponentBuilder(line).create());
            }

            Pages pagination = new Pages("Heap", paginatedLines);
            pagination.send(sender);
            plugin.getPageManager().setPagination(sender.getName(), pagination);
        } catch (InstanceNotFoundException instanceNotFoundException) {
            sendError(sender, NOT_ORACLE_MSG);
        } catch (Exception ex) {
            plugin.getLogger().log(Level.SEVERE, null, ex);
            sendError(sender, "An exception occurred. Please check the server log");
        }

        return true;
    }

    private void onDump(CommandSender sender) {
        try {
            //test if this class is available
            Class.forName("com.sun.management.HotSpotDiagnosticMXBean");

            //can be useful for dumping heaps in binary format
            HotSpotDiagnosticMXBean hostSpot = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);

            Path dumpFile = getNewDumpFile();
            hostSpot.dumpHeap(dumpFile.toAbsolutePath().toString(), DUMP_DEAD_OBJECTS);

            sender.sendMessage(ChatColor.GRAY + "Dump created: " + dumpFile.getFileName());
            sender.sendMessage(ChatColor.GRAY + "You can analyse it using VisualVM");
        } catch (ClassNotFoundException notFoundEx) {
            sendError(sender, NOT_ORACLE_MSG);
        } catch (Exception ex) {
            plugin.getLogger().log(Level.SEVERE, null, ex);
            sendError(sender, "An exception occurred. Please check the server log");
        }
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/dump/ThreadCommand.java
================================================
package com.github.games647.lagmonitor.command.dump;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.Pages;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import javax.management.InstanceNotFoundException;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;

public class ThreadCommand extends DumpCommand {

    private static final String DUMP_COMMAND = "threadPrint";
    private static final String[] EMPTY_STRING_ARRAY = {};

    public ThreadCommand(LagMonitor plugin) {
        super(plugin, "thread", "tdump");
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        if (args.length > 0) {
            String subCommand = args[0];
            if ("dump".equalsIgnoreCase(subCommand)) {
                onDump(sender);
            } else {
                sender.sendMessage(label);
            }

            return true;
        }

        List<BaseComponent[]> lines = new ArrayList<>();

        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
        for (Thread thread : allStackTraces.keySet()) {
            if (thread.getContextClassLoader() == null) {
                //ignore java system threads like reference handler
                continue;
            }

            BaseComponent[] components = new ComponentBuilder("ID-" + thread.getId() + ": ")
                    .color(PRIMARY_COLOR.asBungee())
                    .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND
                            , "/stacktrace " + thread.getName()))
                    .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT
                            , new ComponentBuilder("Show the stacktrace").create()))
                    .append(thread.getName() + ' ')
                    .color(ChatColor.GOLD)
                    .append(thread.getState().toString())
                    .color(SECONDARY_COLOR.asBungee())
                    .create();
            lines.add(components);
        }

        Pages pagination = new Pages("Threads", lines);
        pagination.send(sender);
        plugin.getPageManager().setPagination(sender.getName(), pagination);
        return true;
    }

    private void onDump(CommandSender sender) {
        try {
            String result = invokeDiagnosticCommand(DUMP_COMMAND, EMPTY_STRING_ARRAY);

            Path dumpFile = getNewDumpFile();
            Files.write(dumpFile, Collections.singletonList(result));

            sender.sendMessage(ChatColor.GRAY + "Dump created: " + dumpFile.getFileName());
            sender.sendMessage(ChatColor.GRAY + "You can analyse it using VisualVM");
        } catch (InstanceNotFoundException instanceNotFoundException) {
            sendError(sender, NOT_ORACLE_MSG);
        } catch (Exception ex) {
            plugin.getLogger().log(Level.SEVERE, null, ex);
            sendError(sender, "An exception occurred. Please check the server log");
        }
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/minecraft/PingCommand.java
================================================
package com.github.games647.lagmonitor.command.minecraft;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.command.LagCommand;
import com.github.games647.lagmonitor.util.LagUtils;
import com.github.games647.lagmonitor.util.RollingOverHistory;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

public class PingCommand extends LagCommand {

    public PingCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        if (args.length > 0) {
            displayPingOther(sender, command, args[0]);
        } else if (sender instanceof Player) {
            displayPingSelf(sender);
        } else {
            sendError(sender, "You have to be in game in order to see your own ping");
        }

        return true;
    }

    private void displayPingSelf(CommandSender sender) {
        RollingOverHistory history = plugin.getPingManager().map(m -> m.getHistory(sender.getName())).orElse(null);
        if (history == null) {
            sendError(sender, "Sorry there is currently no data available");
            return;
        }

        int lastPing = (int) history.getLastSample();
        sender.sendMessage(PRIMARY_COLOR + "Your ping is: " + ChatColor.DARK_GREEN + lastPing + "ms");

        float pingAverage = (float) (Math.round(history.getAverage() * 100.0) / 100.0);
        sender.sendMessage(PRIMARY_COLOR + "Average: " + ChatColor.DARK_GREEN + pingAverage + "ms");
    }

    private void displayPingOther(CommandSender sender, Command command, String playerName) {
        if (sender.hasPermission(command.getPermission() + ".other")) {
            RollingOverHistory history = plugin.getPingManager().map(m -> m.getHistory(sender.getName())).orElse(null);
            if (history == null || !canSee(sender, playerName)) {
                sendError(sender, "No data for that player " + playerName);
                return;
            }

            int lastPing = (int) history.getLastSample();

            sender.sendMessage(ChatColor.WHITE + playerName + PRIMARY_COLOR + "'s ping is: "
                    + ChatColor.DARK_GREEN + lastPing + "ms");

            float pingAverage = LagUtils.round(history.getAverage());
            sender.sendMessage(PRIMARY_COLOR + "Average: " + ChatColor.DARK_GREEN + pingAverage + "ms");
        } else {
            sendError(sender, "You don't have enough permission");
        }
    }

    private boolean canSee(CommandSender sender, String playerName) {
        if (sender instanceof Player) {
            return ((Player) sender).canSee(Bukkit.getPlayerExact(playerName));
        }

        return true;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/minecraft/SystemCommand.java
================================================
package com.github.games647.lagmonitor.command.minecraft;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.command.LagCommand;
import com.github.games647.lagmonitor.traffic.TrafficReader;
import com.github.games647.lagmonitor.util.LagUtils;
import com.google.common.base.StandardSystemProperty;

import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadMXBean;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

import oshi.software.os.OSProcess;

import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;

import static com.github.games647.lagmonitor.util.LagUtils.readableBytes;

public class SystemCommand extends LagCommand {

    public SystemCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        displayRuntimeInfo(sender, ManagementFactory.getRuntimeMXBean());
        displayThreadInfo(sender, ManagementFactory.getThreadMXBean());
        displayProcessInfo(sender);
        displayUserInfo(sender);
        displayMinecraftInfo(sender);
        return true;
    }

    private void displayUserInfo(CommandSender sender) {
        sender.sendMessage(PRIMARY_COLOR + "User");

        sendMessage(sender, "    Timezone", System.getProperty("user.timezone", "Unknown"));
        sendMessage(sender, "    Country", System.getProperty("user.country", "Unknown"));
        sendMessage(sender, "    Language", System.getProperty("user.language", "Unknown"));
        sendMessage(sender, "    Home", StandardSystemProperty.USER_HOME.value());
        sendMessage(sender, "    Name", StandardSystemProperty.USER_NAME.value());
    }

    private void displayProcessInfo(CommandSender sender) {
        sender.sendMessage(PRIMARY_COLOR + "Process:");

        Optional<OSProcess> optProcess = plugin.getNativeData().getProcess();
        if (optProcess.isPresent()) {
            OSProcess process = optProcess.get();

            sendMessage(sender, "    PID", String.valueOf(process.getProcessID()));
            sendMessage(sender, "    Name", process.getName());
            sendMessage(sender, "    Path", process.getPath());
            sendMessage(sender, "    Working directory", process.getCurrentWorkingDirectory());
            sendMessage(sender, "    User", process.getUser());
            sendMessage(sender, "    Group", process.getGroup());
        } else {
            sendError(sender, NATIVE_NOT_FOUND);
        }
    }

    private void displayRuntimeInfo(CommandSender sender, RuntimeMXBean runtimeBean) {
        long uptime = runtimeBean.getUptime();
        String uptimeFormat = LagMonitor.formatDuration(Duration.ofMillis(uptime));

        displayMemoryInfo(sender, Runtime.getRuntime());

        // runtime specific
        sendMessage(sender, "Uptime", uptimeFormat);
        sendMessage(sender, "Arguments", runtimeBean.getInputArguments().toString());
        sendMessage(sender, "Classpath", runtimeBean.getClassPath());
        sendMessage(sender, "Library path", runtimeBean.getLibraryPath());
    }

    private void displayThreadInfo(CommandSender sender, ThreadMXBean threadBean) {
        sendMessage(sender, "Threads", String.valueOf(threadBean.getThreadCount()));
        sendMessage(sender, "Peak threads", String.valueOf(threadBean.getPeakThreadCount()));
        sendMessage(sender, "Daemon threads", String.valueOf(threadBean.getDaemonThreadCount()));
        sendMessage(sender, "Total started threads", String.valueOf(threadBean.getTotalStartedThreadCount()));
    }

    private void displayMemoryInfo(CommandSender sender, Runtime runtime) {
        long maxMemory = runtime.maxMemory();
        long freeMemory = runtime.freeMemory();
        long totalMemory = runtime.totalMemory();

        sendMessage(sender, "Reserved used RAM", readableBytes(totalMemory - freeMemory));
        sendMessage(sender, "Reserved free RAM", readableBytes(freeMemory));
        sendMessage(sender, "Reserved RAM", readableBytes(totalMemory));
        sendMessage(sender, "Max RAM", readableBytes(maxMemory));
    }

    private void displayMinecraftInfo(CommandSender sender) {
        //Minecraft specific
        sendMessage(sender, "TPS", String.valueOf(plugin.getTpsHistoryTask().getLastSample()));

        TrafficReader trafficReader = plugin.getTrafficReader();
        if (trafficReader != null) {
            String formattedIncoming = readableBytes(trafficReader.getIncomingBytes().longValue());
            String formattedOutgoing = readableBytes(trafficReader.getOutgoingBytes().longValue());
            sendMessage(sender, "Incoming Traffic", formattedIncoming);
            sendMessage(sender, "Outgoing Traffic", formattedOutgoing);
        }

        Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
        sendMessage(sender, "Loaded Plugins", String.format("%d/%d", getEnabledPlugins(plugins), plugins.length));

        int onlinePlayers = Bukkit.getOnlinePlayers().size();
        int maxPlayers = Bukkit.getMaxPlayers();
        sendMessage(sender, "Players", String.format("%d/%d", onlinePlayers, maxPlayers));

        displayWorldInfo(sender);
        sendMessage(sender, "Server version", Bukkit.getVersion());
    }

    private void displayWorldInfo(CommandSender sender) {
        int entities = 0;
        int chunks = 0;
        int livingEntities = 0;
        int tileEntities = 0;

        long usedWorldSize = 0;

        List<World> worlds = Bukkit.getWorlds();
        for (World world : worlds) {
            for (Chunk loadedChunk : world.getLoadedChunks()) {
                tileEntities += loadedChunk.getTileEntities().length;
            }

            livingEntities += world.getLivingEntities().size();
            entities += world.getEntities().size();
            chunks += world.getLoadedChunks().length;

            File worldFolder = Bukkit.getWorld(world.getUID()).getWorldFolder();
            usedWorldSize += LagUtils.getFolderSize(plugin.getLogger(), worldFolder.toPath());
        }

        sendMessage(sender, "Entities", String.format("%d/%d", livingEntities, entities));
        sendMessage(sender, "Tile Entities", String.valueOf(tileEntities));
        sendMessage(sender, "Loaded Chunks", String.valueOf(chunks));
        sendMessage(sender, "Worlds", String.valueOf(worlds.size()));
        sendMessage(sender, "World Size", readableBytes(usedWorldSize));
    }

    private int getEnabledPlugins(Plugin[] plugins) {
        return (int) Stream.of(plugins).filter(Plugin::isEnabled).count();
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/minecraft/TPSCommand.java
================================================
package com.github.games647.lagmonitor.command.minecraft;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.command.LagCommand;
import com.github.games647.lagmonitor.task.TPSHistoryTask;
import com.google.common.collect.Lists;

import java.text.DecimalFormat;
import java.util.List;
import java.util.stream.IntStream;

import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.util.ChatPaginator;

public class TPSCommand extends LagCommand {

    private static final ChatColor PRIMARY_COLOR = ChatColor.DARK_AQUA;
    private static final ChatColor SECONDARY_COLOR = ChatColor.GRAY;

    private static final char EMPTY_CHAR = ' ';
    private static final char GRAPH_CHAR = '+';

    private static final char PLAYER_EMPTY_CHAR = '▂';
    private static final char PLAYER_GRAPH_CHAR = '▇';

    private static final int GRAPH_WIDTH = 60 / 2;
    private static final int GRAPH_LINES = ChatPaginator.CLOSED_CHAT_PAGE_HEIGHT - 3;

    public TPSCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        List<StringBuilder> graphLines = Lists.newArrayListWithExpectedSize(GRAPH_LINES);
        IntStream.rangeClosed(1, GRAPH_LINES)
                .map(i -> GRAPH_WIDTH * 2)
                .mapToObj(StringBuilder::new)
                .forEach(graphLines::add);

        TPSHistoryTask tpsHistoryTask = plugin.getTpsHistoryTask();

        boolean console = true;
        if (sender instanceof Player) {
            console = false;
        }

        float[] lastSeconds = tpsHistoryTask.getMinuteSample().getSamples();
        int position = tpsHistoryTask.getMinuteSample().getCurrentPosition();
        buildGraph(lastSeconds, position, graphLines, console);
        graphLines.stream().map(Object::toString).forEach(sender::sendMessage);

        printAverageHistory(tpsHistoryTask, sender);
        sender.sendMessage(PRIMARY_COLOR + "Current TPS: " + tpsHistoryTask.getLastSample());
        return true;
    }

    private void printAverageHistory(TPSHistoryTask tpsHistoryTask, CommandSender sender) {
        float minuteAverage = tpsHistoryTask.getMinuteSample().getAverage();
        float quarterAverage = tpsHistoryTask.getQuarterSample().getAverage();
        float halfHourAverage = tpsHistoryTask.getHalfHourSample().getAverage();

        DecimalFormat formatter = new DecimalFormat("###.##");
        sender.sendMessage(PRIMARY_COLOR + "Last Samples (1m, 15m, 30m): " + SECONDARY_COLOR
                + formatter.format(minuteAverage)
                + ' ' + formatter.format(quarterAverage)
                + ' ' + formatter.format(halfHourAverage));
    }

    private void buildGraph(float[] lastSeconds, int lastPos, List<StringBuilder> graphLines, boolean console) {
        int index = lastPos;
        //in x-direction
        for (int xPos = 1; xPos < GRAPH_WIDTH; xPos++) {
            index++;
            if (index == lastSeconds.length) {
                index = 0;
            }

            float sampleSecond = lastSeconds[index];
            buildLine(sampleSecond, graphLines, console);
        }
    }

    private void buildLine(float sampleSecond, List<StringBuilder> graphLines, boolean console) {
        ChatColor color = ChatColor.DARK_RED;
        int lines = 0;
        if (sampleSecond > 19.5F) {
            lines = GRAPH_LINES;
            color = ChatColor.DARK_GREEN;
        } else if (sampleSecond > 18.0F) {
            lines = GRAPH_LINES - 1;
            color = ChatColor.GREEN;
        } else if (sampleSecond > 17.0F) {
            lines = GRAPH_LINES - 2;
            color = ChatColor.YELLOW;
        } else if (sampleSecond > 15.0F) {
            lines = GRAPH_LINES - 3;
            color = ChatColor.GOLD;
        } else if (sampleSecond > 12.0F) {
            lines = GRAPH_LINES - 4;
            color = ChatColor.RED;
        }

        //in y-direction in reverse order
        for (int line = GRAPH_LINES - 1; line >= 0; line--) {
            if (lines == 0) {
                graphLines.get(line).append(ChatColor.WHITE).append(console ? EMPTY_CHAR : PLAYER_EMPTY_CHAR);
                continue;
            }

            lines--;
            graphLines.get(line).append(color).append(console ? GRAPH_CHAR : PLAYER_GRAPH_CHAR);
        }
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/minecraft/TasksCommand.java
================================================
package com.github.games647.lagmonitor.command.minecraft;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.Pages;
import com.github.games647.lagmonitor.command.LagCommand;
import com.github.games647.lagmonitor.traffic.Reflection;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;

import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;

import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitTask;

public class TasksCommand extends LagCommand {

    private static final MethodHandle taskHandle;

    static {
        Class<?> taskClass = Reflection.getCraftBukkitClass("scheduler.CraftTask");

        MethodHandle localHandle = null;
        try {
            localHandle = MethodHandles.publicLookup().findGetter(taskClass, "task", Runnable.class);
        } catch (NoSuchFieldException | IllegalAccessException noSuchFieldEx) {
            //ignore
        }

        taskHandle = localHandle;
    }

    public TasksCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        List<BaseComponent[]> lines = new ArrayList<>();

        List<BukkitTask> pendingTasks = Bukkit.getScheduler().getPendingTasks();
        for (BukkitTask pendingTask : pendingTasks) {
            lines.add(formatTask(pendingTask));

            Class<?> runnableClass = getRunnableClass(pendingTask);
            if (runnableClass != null) {
                lines.add(new ComponentBuilder("    Task: ")
                        .color(PRIMARY_COLOR.asBungee())
                        .append(runnableClass.getSimpleName())
                        .color(SECONDARY_COLOR.asBungee())
                        .create());
            }
        }

        Pages pagination = new Pages("Stacktrace", lines);
        pagination.send(sender);
        plugin.getPageManager().setPagination(sender.getName(), pagination);
        return true;
    }

    private BaseComponent[] formatTask(BukkitTask pendingTask) {
        Plugin owner = pendingTask.getOwner();
        int taskId = pendingTask.getTaskId();
        boolean sync = pendingTask.isSync();

        String id = Integer.toString(taskId);
        if (sync) {
            id += "-Sync";
        } else if (Bukkit.getScheduler().isCurrentlyRunning(taskId)) {
            id += "-Running";
        }

        return new ComponentBuilder(owner.getName())
                .color(PRIMARY_COLOR.asBungee())
                .append('-' + id)
                .color(SECONDARY_COLOR.asBungee())
                .create();
    }

    private Class<?> getRunnableClass(BukkitTask task) {
        try {
            return taskHandle.invokeExact(task).getClass();
        } catch (Exception ex) {
            //ignore
        } catch (Throwable throwable) {
            throw (Error) throwable;
        }

        return null;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/timing/PaperTimingsCommand.java
================================================
package com.github.games647.lagmonitor.command.timing;

import co.aikar.timings.TimingHistory;
import co.aikar.timings.Timings;
import co.aikar.timings.TimingsManager;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.Pages;
import com.github.games647.lagmonitor.traffic.Reflection;
import com.google.common.collect.EvictingQueue;
import com.google.common.collect.Maps;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;

import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;

import static com.github.games647.lagmonitor.util.LagUtils.round;

/**
 * Paper and Sponge uses a new timings system (v2).
 * Missing data:
 * * TicksRecord
 * -> player ticks
 * -> timedTicks
 * -> entityTicks
 * -> activatedEntityTicks
 * -> tileEntityTicks
 * * MinuteReport
 * -> time
 * -> tps
 * -> avgPing
 * -> fullServerTick
 * -> ticks
 * * World data
 * -> worldName
 * -> tileEntities
 * -> entities
 *
 * => This concludes to the fact that the big benefits from Timings v2 isn't available. For example you cannot
 * scroll through your history
 */
public class PaperTimingsCommand extends TimingCommand {

    //TODO: Change to MethodHandles
    private static final String TIMINGS_PACKAGE = "co.aikar.timings";

    private static final String EXPORT_CLASS = TIMINGS_PACKAGE + '.' + "TimingsExport";
    private static final String HANDLER_CLASS = TIMINGS_PACKAGE + '.' + "TimingHandler";
    private static final String HISTORY_ENTRY_CLASS = TIMINGS_PACKAGE + '.' + "TimingHistoryEntry";
    private static final String DATA_CLASS = TIMINGS_PACKAGE + '.' + "TimingData";

    private static final ChatColor HEADER_COLOR = ChatColor.YELLOW;

    private int historyInterval;

    public PaperTimingsCommand(LagMonitor plugin) {
        super(plugin);

        try {
            historyInterval = Reflection.getField("com.destroystokyo.paper.PaperConfig", "config"
            , YamlConfiguration.class).get(null).getInt("timings.history-interval");
        } catch (IllegalArgumentException illegalArgumentException) {
            //cannot find paper spigot
            historyInterval = -1;
        }
    }

    @Override
    protected boolean isTimingsEnabled() {
        return Timings.isTimingsEnabled();
    }

    @Override
    protected void sendTimings(CommandSender sender) {
        EvictingQueue<TimingHistory> history = Reflection.getField(TimingsManager.class, "HISTORY", EvictingQueue.class)
                .get(null);

        TimingHistory lastHistory = history.peek();
        if (lastHistory == null) {
            sendError(sender, "Not enough data collected yet. You need to wait at least 5min after server startup");
            return;
        }

        List<BaseComponent[]> lines = new ArrayList<>();
        printTimings(lines, lastHistory);

        Pages pagination = new Pages("Paper Timings", lines);
        pagination.send(sender);

        plugin.getPageManager().setPagination(sender.getName(), pagination);
    }

    public void printTimings(Collection<BaseComponent[]> lines, TimingHistory lastHistory) {
        printHeadData(lastHistory, lines);

        Map<Integer, String> idHandler = Maps.newHashMap();

        Map<?, ?> groups = Reflection.getField(TIMINGS_PACKAGE + ".TimingIdentifier", "GROUP_MAP", Map.class).get(null);
        for (Object group : groups.values()) {
            String groupName = Reflection.getField(group.getClass(), "name", String.class).get(group);
            Iterable<?> handlers = Reflection.getField(group.getClass(), "handlers", List.class).get(group);
            for (Object handler : handlers) {
                int id = Reflection.getField(HANDLER_CLASS, "id", Integer.TYPE).get(handler);
                Object identifier = Reflection.getField(HANDLER_CLASS, "identifier", Object.class).get(handler);
                String name = Reflection.getField(identifier.getClass(), "name", String.class).get(identifier);
                if (name.contains("Combined")) {
                    idHandler.put(id, "Combined " + groupName);
                } else {
                    idHandler.put(id, name);
                }
            }
        }

        //TimingHistoryEntry
        Object[] entries = Reflection.getField(TimingHistory.class, "entries", Object[].class).get(lastHistory);
        for (Object entry : entries) {
            Object parentData = Reflection.getField(HISTORY_ENTRY_CLASS, "data", Object.class).get(entry);
            int childId = Reflection.getField(DATA_CLASS, "id", Integer.TYPE).get(parentData);

            String handlerName = idHandler.get(childId);
            String parentName;
            if (handlerName == null) {
                parentName = "Unknown-" + childId;
            } else {
                parentName = handlerName;
            }

            int parentCount = Reflection.getField(DATA_CLASS, "count", Integer.TYPE).get(parentData);
            long parentTime = Reflection.getField(DATA_CLASS, "totalTime", Long.TYPE).get(parentData);

//            long parentLagCount = Reflection.getField(DATA_CLASS, "lagCount", Integer.TYPE).get(parentData);
//            long parentLagTime = Reflection.getField(DATA_CLASS, "lagTime", Long.TYPE).get(parentData);
            lines.add(new ComponentBuilder(parentName).color(HEADER_COLOR)
                    .append(" Count: " + parentCount + " Time: " + parentTime).create());

            Object[] children = Reflection.getField(HISTORY_ENTRY_CLASS, "children", Object[].class).get(entry);
            for (Object childData : children) {
                printChildren(parentData, childData, idHandler, lines);
            }
        }
    }

    private void printChildren(Object parent, Object childData, Map<Integer, String> idMap,
                               Collection<BaseComponent[]> lines) {
        int childId = Reflection.getField(DATA_CLASS, "id", Integer.TYPE).get(childData);

        String handlerName = idMap.get(childId);
        String childName;
        if (handlerName == null) {
            childName = "Unknown-" + childId;
        } else {
            childName = handlerName;
        }

        int childCount = Reflection.getField(DATA_CLASS, "count", Integer.TYPE).get(childData);
        long childTime = Reflection.getField(DATA_CLASS, "totalTime", Long.TYPE).get(childData);

        long parentTime = Reflection.getField(DATA_CLASS, "totalTime", Long.TYPE).get(parent);
        double percent = (double) childTime / parentTime;

        lines.add(new ComponentBuilder("    " + childName + " Count: " + childCount + " Time: " + childTime
                + ' ' + round(percent) + '%')
                .color(PRIMARY_COLOR.asBungee()).create());
    }

    private void printHeadData(TimingHistory lastHistory, Collection<BaseComponent[]> lines) {
        // Represents all time spent running the server this history
        long totalTime = Reflection.getField(TimingHistory.class, "totalTime", Long.TYPE).get(lastHistory);
        long totalTicks = Reflection.getField(TimingHistory.class, "totalTicks", Long.TYPE).get(lastHistory);

        long cost = (long) Reflection.getMethod(EXPORT_CLASS, "getCost").invoke(null);
        lines.add(new ComponentBuilder("Cost: ")
                .color(PRIMARY_COLOR.asBungee())
                .append(Long.toString(cost)).color(SECONDARY_COLOR.asBungee()).create());

        double totalSeconds = (double) totalTime / 1000 / 1000;

        long playerTicks = TimingHistory.playerTicks;
        long tileEntityTicks = TimingHistory.tileEntityTicks;
        long activatedEntityTicks = TimingHistory.activatedEntityTicks;
        long entityTicks = TimingHistory.entityTicks;

        double activatedAvgEntities = (double) activatedEntityTicks / totalTicks;
        double totalAvgEntities = (double) entityTicks / totalTicks;

        double averagePlayers = (double) playerTicks / totalTicks;

        double desiredTicks = 20 * historyInterval;
        double averageTicks = totalTicks / desiredTicks * 20;

        String format = ChatColor.DARK_AQUA + "%s" + ' ' + ChatColor.GRAY + "%s";

        //head data
        lines.add(TextComponent.fromLegacyText(String.format(format, "Total (sec):", round(totalSeconds))));
        lines.add(TextComponent.fromLegacyText(String.format(format, "Ticks:", round(totalTicks))));
        lines.add(TextComponent.fromLegacyText(String.format(format, "Avg ticks:", round(averageTicks))));
//        lines.add(TextComponent.fromLegacyText(String.format(format, "Server Load:", round(serverLoad))));
        lines.add(TextComponent.fromLegacyText(String.format(format, "AVG Players:", round(averagePlayers))));

        lines.add(TextComponent.fromLegacyText(String.format(format, "Activated Entities:", round(activatedAvgEntities))
                + " / " + round(totalAvgEntities)));
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/timing/SpigotTimingsCommand.java
================================================
package com.github.games647.lagmonitor.command.timing;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.Pages;
import com.github.games647.lagmonitor.traffic.Reflection;
import com.github.games647.lagmonitor.traffic.Reflection.FieldAccessor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.TimeUnit;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;

import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.TimingsCommand;
import org.spigotmc.CustomTimingsHandler;

import static com.github.games647.lagmonitor.util.LagUtils.round;

/**
 * Parsed from the PHP project by aikar
 * https://github.com/aikar/timings
 */
public class SpigotTimingsCommand extends TimingCommand {

    //these timings will be in the breakdown report
    private static final String EXCLUDE_IDENTIFIER = "** ";
    //TODO: Change to MethodHandles
    public SpigotTimingsCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    protected boolean isTimingsEnabled() {
        return Bukkit.getPluginManager().useTimings();
    }

    @Override
    protected void sendTimings(CommandSender sender) {
        //place sampleTime here to be very accurate
        long sampleTime = System.nanoTime() - TimingsCommand.timingStart;
        if (TimeUnit.NANOSECONDS.toMinutes(sampleTime) <= 5) {
            sendError(sender, "Sampling time is too low");
            return;
        }

        Queue<CustomTimingsHandler> handlers = Reflection.getField(CustomTimingsHandler.class, "HANDLERS", Queue.class)
                .get(null);

        List<BaseComponent[]> lines = new ArrayList<>();
        sendParsedOutput(handlers, lines, sampleTime);

        Pages pagination = new Pages("Paper Timings", lines);
        pagination.send(sender);

        this.plugin.getPageManager().setPagination(sender.getName(), pagination);
    }

    private void sendParsedOutput(Iterable<CustomTimingsHandler> handlers, Collection<BaseComponent[]> lines,
                                  long sampleTime) {
        Map<String, Timing> timings = new HashMap<>();
        Timing breakdownTiming = new Timing("Breakdown", -1, -1);
        Timing minecraftTiming = new Timing("Minecraft");
        timings.put("Minecraft", minecraftTiming);
        timings.put("Breakdown", breakdownTiming);

        parseTimings(handlers, timings, minecraftTiming, breakdownTiming);

        long playerTicks = 0;
        long activatedEntityTicks = 0;
        long entityTicks = 0;
        long numTicks = 0;
        for (Map.Entry<String, Timing> entry : breakdownTiming.getSubCategories().entrySet()) {
            String key = entry.getKey();
            Timing value = entry.getValue();
            if ("** tickEntity - EntityPlayer".equalsIgnoreCase(key)) {
                playerTicks = value.getTotalCount();
            } else if ("** activatedTickEntity".equalsIgnoreCase(key)) {
                activatedEntityTicks = value.getTotalCount();
            } else if ("** tickEntity".equalsIgnoreCase(key)) {
                entityTicks = value.getTotalCount();
            } else if (key.contains(" - entityTick")) {
                numTicks = Math.max(numTicks, value.getTotalCount());
            }
        }

        double serverLoad = 0;

        for (Map.Entry<String, Timing> entry : timings.entrySet()) {
            String category = entry.getKey();
            Timing timing = entry.getValue();
            float pct = (float) timing.getTotalTime() / sampleTime * 100;

            String highlightedPercent = highlightPct(round(pct), 1, 3, 6);
            if (timing == minecraftTiming) {
                highlightedPercent = highlightPct(round(pct), 20, 40, 70);
            }

            //nanoseconds -> seconds
            float totalSeconds = (float) timing.getTotalTime() / 1000 / 1000 / 1000;
            lines.add(TextComponent.fromLegacyText(ChatColor.YELLOW + "=== " + category
                    + " Total: " + round(totalSeconds) + "sec: "
                    + highlightedPercent + "% " + ChatColor.YELLOW + "==="));
            if (timing.getSubCategories() != null) {
                for (Map.Entry<String, Timing> subEntry : timing.getSubCategories().entrySet()) {
                    String event = subEntry.getKey().replace("** ", "").replace("-", "");
                    int lastPackage = event.lastIndexOf('.');
                    if (lastPackage != -1) {
                        event = event.substring(lastPackage + 1);
                    }

                    Timing subValue = subEntry.getValue();

                    double avg = subValue.calculateAverage();
                    double timesPerTick = (double) subValue.getTotalCount() / numTicks;
                    if (timesPerTick > 1) {
                        avg *= timesPerTick;
                    }

                    double pctTick = avg / 1000 / 1000 / 50 * 100;
//                    float count = (float) subValue.getTotalCount() / 1000;
                    //->ms
                    avg = avg / 1000 / 1000;
                    double pctTotal = (double) subValue.getTotalTime() / sampleTime * 100;
                    if ("Full Server Tick".equalsIgnoreCase(event)) {
                        serverLoad = pctTick;
                    }

                    lines.add(TextComponent.fromLegacyText(ChatColor.DARK_AQUA + event + ' '
                            + highlightPct(round(pctTotal), 10, 20, 50)
                            + " Tick: " + highlightPct(round(pctTick), 3, 15, 40)
                            + " AVG: " + round(avg) + "ms"));
                }
            }
        }

        lines.add(new ComponentBuilder("==========================================").color(ChatColor.GOLD).create());

        long total = minecraftTiming.getTotalTime();
        printHeadData(total, activatedEntityTicks, numTicks, entityTicks, playerTicks, sampleTime, lines, serverLoad);
    }

    private void printHeadData(long total, long activatedEntityTicks, long numTicks, long entityTicks, long playerTicks
            , long sampleTime, Collection<BaseComponent[]> lines, double serverLoad) {
        float totalSeconds = (float) total / 1000 / 1000 / 1000;

        float activatedAvgEntities = (float) activatedEntityTicks / numTicks;
        float totalAvgEntities = (float) entityTicks / numTicks;

        float averagePlayers = (float) playerTicks / numTicks;

        float desiredTicks = (float) sampleTime / 1000 / 1000 / 1000 * 20;
        float averageTicks = numTicks / desiredTicks * 20;

        String format = ChatColor.DARK_AQUA + "%s" + ' ' + ChatColor.GRAY + "%s";

        //head data
        lines.add(TextComponent.fromLegacyText(String.format(format, "Total (sec):", round(totalSeconds))));
        lines.add(TextComponent.fromLegacyText(String.format(format, "Ticks:", round(numTicks))));
        lines.add(TextComponent.fromLegacyText(String.format(format, "Avg ticks:", round(averageTicks))));
        lines.add(TextComponent.fromLegacyText(String.format(format, "Server Load:", round(serverLoad))));
        lines.add(TextComponent.fromLegacyText(String.format(format, "AVG Players:", round(averagePlayers))));

        lines.add(TextComponent.fromLegacyText(String.format(format, "Activated Entities:", round(activatedAvgEntities))
                + " / " + round(totalAvgEntities)));

        //convert from nanoseconds to seconds
        String formatted = String.format(format, "Sample Time (sec):", round((float) sampleTime / 1000 / 1000 / 1000));
        lines.add(TextComponent.fromLegacyText(formatted));
    }

    private void parseTimings(Iterable<CustomTimingsHandler> handlers, Map<String, Timing> timings
            , Timing minecraftTiming, Timing breakdownTiming) {
//        FieldAccessor<CustomTimingsHandler> getParent = Reflection
//                .getField(CustomTimingsHandler.class, "parent", CustomTimingsHandler.class);
        FieldAccessor<String> getName = Reflection.getField(CustomTimingsHandler.class, "name", String.class);

        FieldAccessor<Long> getTotalTime = Reflection.getField(CustomTimingsHandler.class, "totalTime", Long.TYPE);
        FieldAccessor<Long> getCount = Reflection.getField(CustomTimingsHandler.class, "count", Long.TYPE);
//        FieldAccessor<Long> getViolations = Reflection.getField(CustomTimingsHandler.class, "violations", Long.TYPE);
        for (CustomTimingsHandler handler : handlers) {
            String subCategory = getName.get(handler);
            long totalTime = getTotalTime.get(handler);
            long count = getCount.get(handler);

            Timing active = minecraftTiming;
            if (subCategory.contains("Event: ")) {
                String pluginName = getProperty(subCategory, "Plugin");
                subCategory = getProperty(subCategory, "Event");

                active = timings.computeIfAbsent(pluginName, Timing::new);
            } else if (subCategory.contains("Task: ")) {
                String pluginName = getProperty(subCategory, "Task");
                subCategory = getProperty(subCategory, "Runnable");

                active = timings.computeIfAbsent(pluginName, Timing::new);
            }

            if (subCategory.startsWith(EXCLUDE_IDENTIFIER)) {
                breakdownTiming.addSubcategory(subCategory, totalTime, count);
            } else {
                active.addSubcategory(subCategory, totalTime, count);
                if (subCategory.startsWith("Task:")) {
                    breakdownTiming.addSubcategory(EXCLUDE_IDENTIFIER + "Tasks", totalTime, count);
                }

                if (active.getTotalTime() >= 0) {
                    active.addTotal(totalTime);
                }
            }
        }
    }

    private String getProperty(String line, String propertyName) {
        String categoryName = propertyName + ": ";

        int startIndex = line.indexOf(categoryName) + categoryName.length();
        int endIndex = line.indexOf(' ', startIndex);
        if (endIndex == -1) {
            //line reached the end
            endIndex = line.length();
        }

        return line.substring(startIndex, endIndex);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/timing/Timing.java
================================================
package com.github.games647.lagmonitor.command.timing;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class Timing implements Comparable<Timing> {

    private final String category;

    private long totalTime;
    private long totalCount;

    private Map<String, Timing> subcategories;

    public Timing(String category) {
        this.category = category;
    }

    public Timing(String category, long totalTime, long count) {
        this.category = category;
        this.totalTime = totalTime;
        this.totalCount = count;
    }

    public String getCategoryName() {
        return category;
    }

    public long getTotalTime() {
        return totalTime;
    }

    public void addTotal(long total) {
        this.totalTime += total;
    }

    public long getTotalCount() {
        return totalCount;
    }

    public void addCount(long count) {
        this.totalCount += count;
    }

    public double calculateAverage() {
        if (totalCount == 0) {
            return 0;
        }

        return (double) totalTime / totalCount;
    }

    public Map<String, Timing> getSubCategories() {
        return subcategories;
    }

    public void addSubcategory(String name, long totalTime, long count) {
        if (subcategories == null) {
            //lazy creating
            subcategories = new HashMap<>();
        }

        Timing timing = subcategories.computeIfAbsent(name, key -> new Timing(key, totalTime, count));
        timing.addTotal(totalTime);
        timing.addCount(totalTime);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Timing timing = (Timing) o;
        return totalTime == timing.totalTime &&
                totalCount == timing.totalCount &&
                Objects.equals(category, timing.category) &&
                Objects.equals(subcategories, timing.subcategories);
    }

    @Override
    public int hashCode() {
        return Objects.hash(category, totalTime, totalCount, subcategories);
    }

    @Override
    public int compareTo(Timing other) {
        return Long.compare(totalTime, other.totalTime);
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + '{' +
                "category='" + category + '\'' +
                ", totalTime=" + totalTime +
                ", totalCount=" + totalCount +
                ", subcategories=" + subcategories +
                '}';
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/command/timing/TimingCommand.java
================================================
package com.github.games647.lagmonitor.command.timing;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.command.LagCommand;

import net.md_5.bungee.api.ChatColor;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;

public abstract class TimingCommand extends LagCommand {

    public TimingCommand(LagMonitor plugin) {
        super(plugin);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!canExecute(sender, command)) {
            return true;
        }

        if (!isTimingsEnabled()) {
            sendError(sender,"The server deactivated timing reports");
            sendError(sender,"Go to paper.yml or spigot.yml and activate timings");
            return true;
        }

        sendTimings(sender);
        return true;
    }

    protected abstract void sendTimings(CommandSender sender);

    protected abstract boolean isTimingsEnabled();

    protected String highlightPct(float percent, int low, int med, int high) {
        ChatColor prefix = ChatColor.GRAY;
        if (percent > high) {
            prefix = ChatColor.DARK_RED;
        } else if (percent > med) {
            prefix = ChatColor.GOLD;
        } else if (percent > low) {
            prefix = ChatColor.YELLOW;
        }

        return prefix + String.valueOf(percent) + '%' + ChatColor.GRAY;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/graph/ClassesGraph.java
================================================
package com.github.games647.lagmonitor.graph;

import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;

import org.bukkit.map.MapCanvas;

public class ClassesGraph extends GraphRenderer {

    private final ClassLoadingMXBean classBean = ManagementFactory.getClassLoadingMXBean();

    public ClassesGraph() {
        super("Classes");
    }

    @Override
    public int renderGraphTick(MapCanvas canvas, int nextPosX) {
        int loadedClasses = classBean.getLoadedClassCount();

        //round up to the nearest multiple of 5
        int roundedMax = (int) (5 * (Math.ceil((float) loadedClasses / 5)));
        int loadedHeight = getHeightScaled(roundedMax, loadedClasses);

        fillBar(canvas, nextPosX, MAX_HEIGHT - loadedHeight, MAX_COLOR);

        //these is the max number
        return loadedClasses;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/graph/CombinedGraph.java
================================================
package com.github.games647.lagmonitor.graph;

import org.bukkit.map.MapCanvas;

public class CombinedGraph extends GraphRenderer {

    private static final int SPACES = 2;

    private final GraphRenderer[] graphRenderers;

    private final int componentWidth;
    private final int[] componentLastPos;

    public CombinedGraph(GraphRenderer... renderers) {
        super("Combined");

        this.graphRenderers = renderers;
        this.componentLastPos = new int[graphRenderers.length];

        //MAX width - spaces between (length - 1) the components
        componentWidth = MAX_WIDTH - (SPACES * (graphRenderers.length - 1)) / graphRenderers.length;
        for (int i = 0; i < componentLastPos.length; i++) {
            componentLastPos[i] = i * componentWidth + i * SPACES;
        }
    }

    @Override
    public int renderGraphTick(MapCanvas canvas, int nextPosX) {
        for (int i = 0; i < graphRenderers.length; i++) {
            GraphRenderer graphRenderer = graphRenderers[i];
            int position = this.componentLastPos[i];
            position++;

            //index starts with 0 so in the end - 1
            int maxComponentWidth = (i + 1) * componentWidth + i * SPACES - 1;
            if (position > maxComponentWidth) {
                //reset it to the start pos
                position = i * componentWidth + i * SPACES;
            }

            graphRenderer.renderGraphTick(canvas, position);
            this.componentLastPos[i] = position;
        }

        return 100;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/graph/CpuGraph.java
================================================
package com.github.games647.lagmonitor.graph;

import com.github.games647.lagmonitor.NativeManager;

import org.bukkit.Bukkit;
import org.bukkit.map.MapCanvas;
import org.bukkit.plugin.Plugin;

public class CpuGraph extends GraphRenderer {

    private final Plugin plugin;
    private final NativeManager nativeData;

    private final Object lock = new Object();

    private int systemHeight;
    private int processHeight;

    public CpuGraph(Plugin plugin, NativeManager nativeData) {
        super("CPU Usage");

        this.plugin = plugin;
        this.nativeData = nativeData;
    }

    @Override
    public int renderGraphTick(MapCanvas canvas, int nextPosX) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            int systemLoad = (int) (nativeData.getCPULoad() * 100);
            int processLOad = (int) (nativeData.getProcessCPULoad() * 100);

            int localSystemHeight = getHeightScaled(100, systemLoad);
            int localProcessHeight = getHeightScaled(100, processLOad);

            //flush updates
            synchronized (lock) {
                this.systemHeight = localSystemHeight;
                this.processHeight = localProcessHeight;
            }
        });

        //read it only one time
        int localSystemHeight;
        int localProcessHeight;
        synchronized (lock) {
            localSystemHeight = this.systemHeight;
            localProcessHeight = this.processHeight;
        }

        fillBar(canvas, nextPosX, MAX_HEIGHT - localSystemHeight, MAX_COLOR);
        fillBar(canvas, nextPosX, MAX_HEIGHT - localProcessHeight, USED_COLOR);

        //set max height as 100%
        return 100;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/graph/GraphRenderer.java
================================================
package com.github.games647.lagmonitor.graph;

import org.bukkit.entity.Player;
import org.bukkit.map.MapCanvas;
import org.bukkit.map.MapPalette;
import org.bukkit.map.MapRenderer;
import org.bukkit.map.MapView;
import org.bukkit.map.MinecraftFont;

public abstract class GraphRenderer extends MapRenderer {

    protected static final int TEXT_HEIGHT = MinecraftFont.Font.getHeight();

    //max height and width = 128 (index from 0-127)
    protected static final int MAX_WIDTH = 128;
    protected static final int MAX_HEIGHT = 128;

    //orange
    protected static final byte MAX_COLOR = MapPalette.matchColor(235, 171, 96);

    //blue
    protected static final byte USED_COLOR = MapPalette.matchColor(105, 182, 212);

    private int nextUpdate;
    private int nextPosX;

    private final String title;

    public GraphRenderer(String title) {
        this.title = title;
    }

    @Override
    public void render(MapView map, MapCanvas canvas, Player player) {
        if (nextUpdate <= 0) {
            //paint only every half seconds (20 Ticks / 2)
            nextUpdate = 10;

            if (nextPosX >= MAX_WIDTH) {
                //start again from the beginning
                nextPosX = 0;
            }

            clearBar(canvas, nextPosX);
            //make it more visual where the renderer is at the moment
            clearBar(canvas, nextPosX + 1);
            int maxValue = renderGraphTick(canvas, nextPosX);

            //override the color
            drawText(canvas, MAX_WIDTH / 2, MAX_HEIGHT / 2, title);

            //count indicators
            String maxText = Integer.toString(maxValue);
            drawText(canvas, MAX_WIDTH - Math.floorDiv(getTextWidth(maxText), 2), TEXT_HEIGHT, maxText);

            String midText = Integer.toString(maxValue / 2);
            drawText(canvas, MAX_WIDTH - Math.floorDiv(getTextWidth(midText), 2), MAX_HEIGHT / 2, midText);

            String zeroText = Integer.toString(0);
            drawText(canvas, MAX_WIDTH - Math.floorDiv(getTextWidth(zeroText), 2), MAX_HEIGHT, zeroText);

            nextPosX++;
        }

        nextUpdate--;
    }

    public abstract int renderGraphTick(MapCanvas canvas, int nextPosX);

    protected int getHeightScaled(int maxValue, int value) {
        return MAX_HEIGHT * value / maxValue;
    }

    protected void clearBar(MapCanvas canvas, int posX) {
        //resets the complete y coordinates on this x in order to free unused
        for (int yPos = 0; yPos < MAX_HEIGHT; yPos++) {
            canvas.setPixel(posX, yPos, (byte) 0);
        }
    }

    protected void clearMap(MapCanvas canvas) {
        for (int xPos = 0; xPos < MAX_WIDTH; xPos++) {
            fillBar(canvas, xPos, 0, (byte) 0);
        }
    }

    protected void fillBar(MapCanvas canvas, int xPos, int yStart, byte color) {
        for (int yPos = yStart; yPos < MAX_HEIGHT; yPos++) {
            canvas.setPixel(xPos, yPos, color);
        }
    }

    protected void drawText(MapCanvas canvas, int midX, int midY, String text) {
        int textWidth = getTextWidth(text);
        canvas.drawText(midX - (textWidth / 2), midY - (TEXT_HEIGHT / 2), MinecraftFont.Font, text);
    }

    private int getTextWidth(String text) {
        return MinecraftFont.Font.getWidth(text);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/graph/HeapGraph.java
================================================
package com.github.games647.lagmonitor.graph;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;

import org.bukkit.map.MapCanvas;

public class HeapGraph extends GraphRenderer {

    private final MemoryMXBean heapUsage = ManagementFactory.getMemoryMXBean();

    public HeapGraph() {
        super("HeapUsage (MB)");
    }

    @Override
    public int renderGraphTick(MapCanvas canvas, int nextPosX) {
        //byte -> mega byte
        int max = (int) (heapUsage.getHeapMemoryUsage().getCommitted() / 1024 / 1024);
        int used = (int) (heapUsage.getHeapMemoryUsage().getUsed() / 1024 / 1024);

        //round to the next 100 e.g. 801 -> 900
        int roundedMax = ((max + 99) / 100) * 100;

        int maxHeight = getHeightScaled(roundedMax, max);
        int usedHeight = getHeightScaled(roundedMax, used);

        //x=0 y=0 is the left top point so convert it
        int convertedMaxHeight = MAX_HEIGHT - maxHeight;
        int convertedUsedHeight = MAX_HEIGHT - usedHeight;

        fillBar(canvas, nextPosX, convertedMaxHeight, MAX_COLOR);
        fillBar(canvas, nextPosX, convertedUsedHeight, USED_COLOR);

        return maxHeight;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/graph/ThreadsGraph.java
================================================
package com.github.games647.lagmonitor.graph;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;

import org.bukkit.map.MapCanvas;

public class ThreadsGraph extends GraphRenderer {

    private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();

    public ThreadsGraph() {
        super("Thread activity");
    }

    @Override
    public int renderGraphTick(MapCanvas canvas, int nextPosX) {
        int threadCount = threadBean.getThreadCount();
        int daemonCount = threadBean.getDaemonThreadCount();

        //round up to the nearest multiple of 5
        int roundedMax = (int) (5 * (Math.ceil((float) threadCount / 5)));
        int threadHeight = getHeightScaled(roundedMax, threadCount);
        int daemonHeight = getHeightScaled(roundedMax, daemonCount);

        fillBar(canvas, nextPosX, MAX_HEIGHT - threadHeight, MAX_COLOR);
        fillBar(canvas, nextPosX, MAX_HEIGHT - daemonHeight, USED_COLOR);

        //these is the max number of all threads
        return threadCount;
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/listener/BlockingConnectionSelector.java
================================================
package com.github.games647.lagmonitor.listener;

import com.github.games647.lagmonitor.threading.BlockingActionManager;
import com.github.games647.lagmonitor.threading.Injectable;

import java.io.IOException;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

public class BlockingConnectionSelector extends ProxySelector implements Injectable {

    private static final Pattern WWW_PATERN = Pattern.compile("www", Pattern.LITERAL);

    private final BlockingActionManager actionManager;
    private ProxySelector oldProxySelector;

    public BlockingConnectionSelector(BlockingActionManager actionManager) {
        this.actionManager = actionManager;
    }

    @Override
    public List<Proxy> select(URI uri) {
        String url = WWW_PATERN.matcher(uri.toString()).replaceAll("");
        if (uri.getScheme().startsWith("http") || (uri.getPort() != 80 && uri.getPort() != 443)) {
            actionManager.checkBlockingAction("Socket: " + url);
        }

        return oldProxySelector == null ? Collections.singletonList(Proxy.NO_PROXY) : oldProxySelector.select(uri);
    }

    @Override
    public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
        if (oldProxySelector != null) {
            oldProxySelector.connectFailed(uri, sa, ioe);
        }
    }

    @Override
    public void inject() {
        ProxySelector proxySelector = ProxySelector.getDefault();
        if (proxySelector != this) {
            oldProxySelector = proxySelector;
            ProxySelector.setDefault(this);
        }
    }

    @Override
    public void restore() {
        if (ProxySelector.getDefault() == this) {
            ProxySelector.setDefault(oldProxySelector);
        }
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/listener/GraphListener.java
================================================
package com.github.games647.lagmonitor.listener;

import com.github.games647.lagmonitor.graph.GraphRenderer;
import com.github.games647.lagmonitor.util.LagUtils;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.MapMeta;
import org.bukkit.map.MapView;

public class GraphListener implements Listener {

    private final boolean mainHandSupported;

    public GraphListener() {
        boolean mainHandMethodEx = false;
        try {
            MethodType type = MethodType.methodType(ItemStack.class);
            MethodHandles.publicLookup().findVirtual(PlayerInventory.class, "getItemInMainHand", type);
            mainHandMethodEx = true;
        } catch (ReflectiveOperationException notFoundEx) {
            //default to false
        }

        this.mainHandSupported = mainHandMethodEx;
    }

    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
    public void onInteract(PlayerInteractEvent clickEvent) {
        Player player = clickEvent.getPlayer();
        PlayerInventory inventory = player.getInventory();

        ItemStack mainHandItem;
        if (mainHandSupported) {
            mainHandItem = inventory.getItemInMainHand();
        } else {
            mainHandItem = inventory.getItemInHand();
        }

        if (isOurGraph(mainHandItem)) {
            inventory.setItemInMainHand(new ItemStack(Material.AIR));
        }
    }

    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
    public void onDrop(PlayerDropItemEvent dropItemEvent) {
        Item itemDrop = dropItemEvent.getItemDrop();
        ItemStack mapItem = itemDrop.getItemStack();
        if (isOurGraph(mapItem)) {
            mapItem.setAmount(0);
        }
    }

    private boolean isOurGraph(ItemStack item) {
        if (!LagUtils.isFilledMapSupported()) {
            return isOurGraphLegacy(item);
        }

        if (item.getType() != Material.FILLED_MAP) {
            return false;
        }

        ItemMeta meta = item.getItemMeta();
        if (!(meta instanceof MapMeta)) {
            return false;
        }

        MapMeta mapMeta = (MapMeta) meta;
        MapView mapView = mapMeta.getMapView();
        return mapView != null && isOurRenderer(mapView);
    }

    private boolean isOurGraphLegacy(ItemStack mapItem) {
        if (mapItem.getType() != Material.MAP)
            return false;

        short mapId = mapItem.getDurability();
        MapView mapView = Bukkit.getMap(mapId);
        return mapView != null && isOurRenderer(mapView);
    }

    private boolean isOurRenderer(MapView mapView) {
        return mapView.getRenderers().stream()
                .anyMatch(GraphRenderer.class::isInstance);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/listener/PageManager.java
================================================
package com.github.games647.lagmonitor.listener;

import com.github.games647.lagmonitor.Pages;

import java.util.HashMap;
import java.util.Map;

import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;

public class PageManager implements Listener {

    private final Map<String, Pages> pages = new HashMap<>();

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent quitEvent) {
        pages.remove(quitEvent.getPlayer().getName());
    }

    public Pages getPagination(String username) {
        return pages.get(username);
    }

    public void setPagination(String username, Pages pagination) {
        pages.put(username, pagination);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/listener/ThreadSafetyListener.java
================================================
package com.github.games647.lagmonitor.listener;

import com.github.games647.lagmonitor.threading.BlockingActionManager;

import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.event.world.SpawnChangeEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldSaveEvent;
import org.bukkit.event.world.WorldUnloadEvent;

/**
 * We can listen to events which are intended to run sync to the main thread.
 * If those events are fired on a async task the operation was likely not thread-safe.
 */
public class ThreadSafetyListener implements Listener {

    private final BlockingActionManager actionManager;

    public ThreadSafetyListener(BlockingActionManager actionManager) {
        this.actionManager = actionManager;
    }

    @EventHandler
    public void onCommand(PlayerCommandPreprocessEvent commandEvent) {
        checkSafety(commandEvent);
    }

    @EventHandler
    public void onInventoryOpen(InventoryOpenEvent inventoryOpenEvent) {
        checkSafety(inventoryOpenEvent);
    }

    @EventHandler
    public void onPlayerMove(PlayerMoveEvent moveEvent) {
        checkSafety(moveEvent);
    }

    @EventHandler
    public void onPlayerTeleport(PlayerTeleportEvent teleportEvent) {
        checkSafety(teleportEvent);
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent joinEvent) {
        checkSafety(joinEvent);
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent quitEvent) {
        checkSafety(quitEvent);
    }

    @EventHandler
    public void onItemHeldChange(PlayerItemHeldEvent itemHeldEvent) {
        checkSafety(itemHeldEvent);
    }

    @EventHandler
    public void onBlockPhysics(BlockPhysicsEvent blockPhysicsEvent) {
        checkSafety(blockPhysicsEvent);
    }

    @EventHandler
    public void onBlockFromTo(BlockFromToEvent blockFromToEvent) {
        checkSafety(blockFromToEvent);
    }

    @EventHandler
    public void onCreatureSpawn(CreatureSpawnEvent creatureSpawnEvent) {
        checkSafety(creatureSpawnEvent);
    }

    @EventHandler
    public void onItemSpawn(ItemSpawnEvent itemSpawnEvent) {
        checkSafety(itemSpawnEvent);
    }

    @EventHandler
    public void onChunkLoad(ChunkLoadEvent chunkLoadEvent) {
        checkSafety(chunkLoadEvent);
    }

    @EventHandler
    public void onChunkUnload(ChunkUnloadEvent chunkUnloadEvent) {
        checkSafety(chunkUnloadEvent);
    }

    @EventHandler
    public void onWorldLoad(WorldLoadEvent worldLoadEvent) {
        checkSafety(worldLoadEvent);
    }

    @EventHandler
    public void onWorldSave(WorldSaveEvent worldSaveEvent) {
        checkSafety(worldSaveEvent);
    }

    @EventHandler
    public void onWorldUnload(WorldUnloadEvent worldUnloadEvent) {
        checkSafety(worldUnloadEvent);
    }

    @EventHandler
    public void onPluginEnable(PluginEnableEvent pluginEnableEvent) {
        checkSafety(pluginEnableEvent);
    }

    @EventHandler
    public void onPluginDisable(PluginDisableEvent pluginDisableEvent) {
        checkSafety(pluginDisableEvent);
    }

    @EventHandler
    public void onSpawnChange(SpawnChangeEvent spawnChangeEvent) {
        checkSafety(spawnChangeEvent);
    }

    @EventHandler
    public void onSpawnChange(EntitySpawnEvent spawnEvent) {
        checkSafety(spawnEvent);
    }

    private void checkSafety(Event eventType) {
        //async executing of sync event
        String eventName = eventType.getEventName();
        if (!eventType.isAsynchronous()) {
            actionManager.checkThreadSafety(eventName);
        }
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/logging/ForwardLogService.java
================================================
package com.github.games647.lagmonitor.logging;

import org.slf4j.ILoggerFactory;
import org.slf4j.IMarkerFactory;
import org.slf4j.jul.JULServiceProvider;
import org.slf4j.spi.MDCAdapter;
import org.slf4j.spi.SLF4JServiceProvider;

public class ForwardLogService implements SLF4JServiceProvider {

    private ILoggerFactory loggerFactory;
    private JULServiceProvider delegate;

    public ILoggerFactory getLoggerFactory() {
        return this.loggerFactory;
    }

    public IMarkerFactory getMarkerFactory() {
        return delegate.getMarkerFactory();
    }

    public MDCAdapter getMDCAdapter() {
        return delegate.getMDCAdapter();
    }

    @Override
    public String getRequesteApiVersion() {
        return delegate.getRequestedApiVersion();
    }

    public String getRequestedApiVersion() {
        return delegate.getRequestedApiVersion();
    }

    public void initialize() {
        this.delegate = new JULServiceProvider();
        this.loggerFactory = new ForwardingLoggerFactory();
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/logging/ForwardingLoggerFactory.java
================================================
package com.github.games647.lagmonitor.logging;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.jul.JDK14LoggerAdapter;

public class ForwardingLoggerFactory implements ILoggerFactory {

    private final ConcurrentMap<String, Logger> loggerMap = new ConcurrentHashMap<>();

    public static java.util.logging.Logger PARENT_LOGGER;

    @Override
    public Logger getLogger(String name) {
        return loggerMap.computeIfAbsent(name, key -> {
            java.util.logging.Logger julLogger;
            if (PARENT_LOGGER == null) {
                julLogger = java.util.logging.Logger.getLogger(name);
            } else {
                julLogger = PARENT_LOGGER;
            }

            Logger newInstance = null;
            try {
                newInstance = createJDKLogger(julLogger);
            } catch (NoSuchMethodException | InvocationTargetException |
                     InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
                System.out.println("Failed to created logging instance");
            }

            return newInstance;
        });
    }

    protected static Logger createJDKLogger(java.util.logging.Logger parent)
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Class<?> adapterClass = JDK14LoggerAdapter.class;
        Constructor<?> cons = adapterClass.getDeclaredConstructor(java.util.logging.Logger.class);
        cons.setAccessible(true);
        return (Logger) cons.newInstance(parent);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/ping/PaperPing.java
================================================
package com.github.games647.lagmonitor.ping;

import org.bukkit.entity.Player;

public class PaperPing implements PingFetcher {

    @Override
    public boolean isAvailable() {
        try {
            //Only available in Paper
            Player.Spigot.class.getDeclaredMethod("getPing");
            return true;
        } catch (NoSuchMethodException noSuchMethodEx) {
            return false;
        }
    }

    @Override
    public int getPing(Player player) {
        return player.spigot().getPing();
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/ping/PingFetcher.java
================================================
package com.github.games647.lagmonitor.ping;

import org.bukkit.entity.Player;

public interface PingFetcher {

    boolean isAvailable();

    int getPing(Player player);
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/ping/ReflectionPing.java
================================================
package com.github.games647.lagmonitor.ping;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.traffic.Reflection;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

public class ReflectionPing implements PingFetcher {

    private static final MethodHandle pingFromPlayerHandle;

    static {
        MethodHandle localPing = null;
        Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
        String playerClazz = "EntityPlayer";
        Class<?> entityPlayer = Reflection.getMinecraftClass(playerClazz, "level." + playerClazz);

        Lookup lookup = MethodHandles.publicLookup();
        try {
            MethodType type = MethodType.methodType(entityPlayer);
            MethodHandle getHandle = lookup.findVirtual(craftPlayerClass, "getHandle", type);
            MethodHandle pingField = lookup.findGetter(entityPlayer, "ping", Integer.TYPE);

            // combine the handles to invoke it only once
            // *getPing(getHandle*) -> add the result of getHandle to the next getPing call
            // a call to this handle will get the ping from a player instance
            localPing = MethodHandles.collectArguments(pingField, 0, getHandle)
                    // allow interface with invokeExact
                    .asType(MethodType.methodType(int.class, Player.class));
        } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException reflectiveEx) {
            Logger logger = JavaPlugin.getPlugin(LagMonitor.class).getLogger();
            logger.log(Level.WARNING, "Cannot find ping field/method", reflectiveEx);
        }

        pingFromPlayerHandle = localPing;
    }

    @Override
    public boolean isAvailable() {
        return pingFromPlayerHandle != null;
    }

    @Override
    public int getPing(Player player) {
        try {
            return (int) pingFromPlayerHandle.invokeExact(player);
        } catch (Exception ex) {
            return -1;
        } catch (Throwable throwable) {
            throw (Error) throwable;
        }
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/ping/SpigotPing.java
================================================
package com.github.games647.lagmonitor.ping;

import org.bukkit.entity.Player;

public class SpigotPing implements PingFetcher {

    @Override
    public boolean isAvailable() {
        try {
            //Only available in Paper
            Player.class.getDeclaredMethod("getPing");
            return true;
        } catch (NoSuchMethodException noSuchMethodEx) {
            return false;
        }
    }

    @Override
    public int getPing(Player player) {
        return player.getPing();
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/storage/MonitorSaveTask.java
================================================
package com.github.games647.lagmonitor.storage;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.NativeManager;
import com.github.games647.lagmonitor.util.LagUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;

import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;

import static com.github.games647.lagmonitor.util.LagUtils.round;

public class MonitorSaveTask implements Runnable {

    protected final LagMonitor plugin;
    protected final Storage storage;

    public MonitorSaveTask(LagMonitor plugin, Storage storage) {
        this.plugin = plugin;
        this.storage = storage;
    }

    @Override
    public void run() {
        try {
            int monitorId = save();
            if (monitorId == -1) {
                //error occurred
                return;
            }

            Map<UUID, WorldData> worldsData = getWorldData();
            if (!storage.saveWorlds(monitorId, worldsData.values())) {
                //error occurred
                return;
            }

            List<PlayerData> playerData = getPlayerData(worldsData);
            storage.savePlayers(playerData);
        } catch (ExecutionException | InterruptedException ex) {
            plugin.getLogger().log(Level.SEVERE, "Error saving monitoring data", ex);
        }
    }

    private List<PlayerData> getPlayerData(final Map<UUID, WorldData> worldsData)
            throws InterruptedException, ExecutionException {
        Future<List<PlayerData>> playerFuture = Bukkit.getScheduler()
                .callSyncMethod(plugin, () -> {
                    Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
                    List<PlayerData> playerData = Lists.newArrayListWithCapacity(onlinePlayers.size());
                    for (Player player : onlinePlayers) {
                        UUID worldId = player.getWorld().getUID();

                        int worldRowId = 0;
                        WorldData worldData = worldsData.get(worldId);
                        if (worldData != null) {
                            worldRowId = worldData.getRowId();
                        }

                        String name = player.getName();
                        int lastPing = plugin.getPingManager().map(m -> ((int) m.getHistory(name).getLastSample()))
                                .orElse(-1);

                        UUID playerId = player.getUniqueId();
                        playerData.add(new PlayerData(worldRowId, playerId, name, lastPing));
                    }

                    return playerData;
                });
        
        return playerFuture.get();
    }

    private Map<UUID, WorldData> getWorldData()
            throws ExecutionException, InterruptedException {
        //this is not thread-safe and have to run sync

        Future<Map<UUID, WorldData>> worldFuture = Bukkit.getScheduler()
                .callSyncMethod(plugin, () -> {
                            List<World> worlds = Bukkit.getWorlds();
                            Map<UUID, WorldData> worldsData = Maps.newHashMapWithExpectedSize(worlds.size());
                            for (World world : worlds) {
                                worldsData.put(world.getUID(), WorldData.fromWorld(world));
                            }

                            return worldsData;
                        });

        Map<UUID, WorldData> worldsData = worldFuture.get();

        //this can run async because it's thread-safe
        worldsData.values().parallelStream()
                .forEach(data -> {
                    Path worldFolder = Bukkit.getWorld(data.getWorldName()).getWorldFolder().toPath();

                    int worldSize = LagUtils.byteToMega(LagUtils.getFolderSize(plugin.getLogger(), worldFolder));
                    data.setWorldSize(worldSize);
                });

        return worldsData;
    }

    private int save() {
        Runtime runtime = Runtime.getRuntime();
        int maxMemory = LagUtils.byteToMega(runtime.maxMemory());
        //we need the free ram not the free heap
        int usedRam = LagUtils.byteToMega(runtime.totalMemory() - runtime.freeMemory());
        int freeRam = maxMemory - usedRam;

        float freeRamPct = round((freeRam * 100) / maxMemory, 4);

        OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
        float loadAvg = round(osBean.getSystemLoadAverage(), 4);
        if (loadAvg < 0) {
            //windows doesn't support this
            loadAvg = 0;
        }

        NativeManager nativeData = plugin.getNativeData();
        float systemUsage = round(nativeData.getCPULoad() * 100, 4);
        float processUsage = round(nativeData.getProcessCPULoad() * 100, 4);

        int totalOsMemory = LagUtils.byteToMega(nativeData.getTotalMemory());
        int freeOsRam = LagUtils.byteToMega(nativeData.getFreeMemory());

        float freeOsRamPct = round((freeOsRam * 100) / totalOsMemory, 4);
        return storage.saveMonitor(processUsage, systemUsage, freeRam, freeRamPct, freeOsRam, freeOsRamPct, loadAvg);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/storage/NativeSaveTask.java
================================================
package com.github.games647.lagmonitor.storage;

import com.github.games647.lagmonitor.LagMonitor;
import com.github.games647.lagmonitor.traffic.TrafficReader;
import com.github.games647.lagmonitor.util.LagUtils;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Optional;

import oshi.SystemInfo;
import oshi.hardware.NetworkIF;
import oshi.software.os.OSProcess;

import static com.github.games647.lagmonitor.util.LagUtils.round;

public class NativeSaveTask implements Runnable {

    private final LagMonitor plugin;
    private final Storage storage;

    private Instant lastCheck = Instant.now();

    private int lastMcRead;
    private int lastMcWrite;
    private int lastDiskRead;
    private int lastDiskWrite;
    private int lastNetRead;
    private int lastNetWrite;

    public NativeSaveTask(LagMonitor plugin, Storage storage) {
        this.plugin = plugin;
        this.storage = storage;
    }

    @Override
    public void run() {
        Instant currentTime = Instant.now();
        int timeDiff = (int) Duration.between(lastCheck, currentTime).getSeconds();

        int mcReadDiff = 0;
        int mcWriteDiff = 0;

        TrafficReader trafficReader = plugin.getTrafficReader();
        if (trafficReader != null) {
            int mcRead = LagUtils.byteToMega(trafficReader.getIncomingBytes().longValue());
            mcReadDiff = getDifference(mcRead, lastMcRead, timeDiff);
            lastMcRead = mcRead;

            int mcWrite = LagUtils.byteToMega(trafficReader.getOutgoingBytes().longValue());
            mcWriteDiff = getDifference(mcWrite, lastMcWrite, timeDiff);
            lastMcWrite = mcWrite;
        }

        int totalSpace = LagUtils.byteToMega(plugin.getNativeData().getTotalSpace());
        int freeSpace = LagUtils.byteToMega(plugin.getNativeData().getFreeSpace());

        //4 decimal places -> Example: 0.2456
        float freeSpacePct = round((freeSpace * 100 / (float) totalSpace), 4);

        int diskReadDiff = 0;
        int diskWriteDiff = 0;
        int netReadDiff = 0;
        int netWriteDiff = 0;

        Optional<SystemInfo> systemInfo = plugin.getNativeData().getSystemInfo();
        if (systemInfo.isPresent()) {
            List<NetworkIF> networkIfs = systemInfo.get().getHardware().getNetworkIFs();
            if (!networkIfs.isEmpty()) {
                NetworkIF networkInterface = networkIfs.get(0);

                int netRead = LagUtils.byteToMega(networkInterface.getBytesRecv());
                netReadDiff = getDifference(netRead, lastNetRead, timeDiff);
                lastNetRead = netRead;

                int netWrite = LagUtils.byteToMega(networkInterface.getBytesSent());
                netWriteDiff = getDifference(netWrite, lastNetWrite, timeDiff);
                lastNetWrite = netWrite;
            }

            Path root = Paths.get(".").getRoot();
            Optional<OSProcess> optProcess = plugin.getNativeData().getProcess();
            if (root != null && optProcess.isPresent()) {
                OSProcess process = optProcess.get();
                String rootFileSystem = root.toAbsolutePath().toString();

                int diskRead = LagUtils.byteToMega(process.getBytesRead());
                diskReadDiff = getDifference(diskRead, lastDiskRead, timeDiff);
                lastDiskRead = diskRead;

                int diskWrite = LagUtils.byteToMega(process.getBytesWritten());
                diskWriteDiff = getDifference(diskWrite, lastDiskWrite, timeDiff);
                lastDiskWrite = diskWrite;
            }
        }

        lastCheck = currentTime;
        storage.saveNative(mcReadDiff, mcWriteDiff, freeSpace, freeSpacePct, diskReadDiff, diskWriteDiff
                , netReadDiff, netWriteDiff);
    }

    private int getDifference(long newVal, long oldVal, long timeDiff) {
        return (int) ((newVal - oldVal) / timeDiff);
    }
}


================================================
FILE: src/main/java/com/github/games647/lagmonitor/storage/PlayerData.java
================================================
package com.github.games647.lagmonitor.storage;

import java.util.UUID;

public class PlayerData {

    private final int worldId;
    private final UUID uuid;
    private final String playerName;
    private final int ping;

    public PlayerData(int worldId, UUID uuid, String playerName, int ping) {
        this.worldId = worldId;
        this.uuid = uuid;
        this.playerName = playerName;

        if (ping < 0) {
            this.ping = Integer.MAX_VALUE;
        } else {
            this.ping = ping;
        }
    }

    public int getWorldId() {
        return worldId;
    }

    public UUID getUuid() {
        return uuid;
    }

    public String getPlayerName() {
        return playerName;
    }

    public int getPing() {
        return ping;
    }

    @Override
Download .txt
gitextract_smk98iqd/

├── .github/
│   └── dependabot.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── pom.xml
└── src/
    ├── main/
    │   ├── java/
    │   │   └── com/
    │   │       └── github/
    │   │           └── games647/
    │   │               └── lagmonitor/
    │   │                   ├── LagMonitor.java
    │   │                   ├── MethodMeasurement.java
    │   │                   ├── NativeManager.java
    │   │                   ├── Pages.java
    │   │                   ├── command/
    │   │                   │   ├── EnvironmentCommand.java
    │   │                   │   ├── GraphCommand.java
    │   │                   │   ├── HelpCommand.java
    │   │                   │   ├── LagCommand.java
    │   │                   │   ├── MbeanCommand.java
    │   │                   │   ├── MonitorCommand.java
    │   │                   │   ├── NativeCommand.java
    │   │                   │   ├── NetworkCommand.java
    │   │                   │   ├── PaginationCommand.java
    │   │                   │   ├── StackTraceCommand.java
    │   │                   │   ├── VmCommand.java
    │   │                   │   ├── dump/
    │   │                   │   │   ├── DumpCommand.java
    │   │                   │   │   ├── FlightCommand.java
    │   │                   │   │   ├── HeapCommand.java
    │   │                   │   │   └── ThreadCommand.java
    │   │                   │   ├── minecraft/
    │   │                   │   │   ├── PingCommand.java
    │   │                   │   │   ├── SystemCommand.java
    │   │                   │   │   ├── TPSCommand.java
    │   │                   │   │   └── TasksCommand.java
    │   │                   │   └── timing/
    │   │                   │       ├── PaperTimingsCommand.java
    │   │                   │       ├── SpigotTimingsCommand.java
    │   │                   │       ├── Timing.java
    │   │                   │       └── TimingCommand.java
    │   │                   ├── graph/
    │   │                   │   ├── ClassesGraph.java
    │   │                   │   ├── CombinedGraph.java
    │   │                   │   ├── CpuGraph.java
    │   │                   │   ├── GraphRenderer.java
    │   │                   │   ├── HeapGraph.java
    │   │                   │   └── ThreadsGraph.java
    │   │                   ├── listener/
    │   │                   │   ├── BlockingConnectionSelector.java
    │   │                   │   ├── GraphListener.java
    │   │                   │   ├── PageManager.java
    │   │                   │   └── ThreadSafetyListener.java
    │   │                   ├── logging/
    │   │                   │   ├── ForwardLogService.java
    │   │                   │   └── ForwardingLoggerFactory.java
    │   │                   ├── ping/
    │   │                   │   ├── PaperPing.java
    │   │                   │   ├── PingFetcher.java
    │   │                   │   ├── ReflectionPing.java
    │   │                   │   └── SpigotPing.java
    │   │                   ├── storage/
    │   │                   │   ├── MonitorSaveTask.java
    │   │                   │   ├── NativeSaveTask.java
    │   │                   │   ├── PlayerData.java
    │   │                   │   ├── Storage.java
    │   │                   │   ├── TPSSaveTask.java
    │   │                   │   └── WorldData.java
    │   │                   ├── task/
    │   │                   │   ├── IODetectorTask.java
    │   │                   │   ├── MonitorTask.java
    │   │                   │   ├── PingManager.java
    │   │                   │   └── TPSHistoryTask.java
    │   │                   ├── threading/
    │   │                   │   ├── BlockingActionManager.java
    │   │                   │   ├── BlockingSecurityManager.java
    │   │                   │   ├── Injectable.java
    │   │                   │   └── PluginViolation.java
    │   │                   ├── traffic/
    │   │                   │   ├── CleanUpTask.java
    │   │                   │   ├── Reflection.java
    │   │                   │   ├── TinyProtocol.java
    │   │                   │   └── TrafficReader.java
    │   │                   └── util/
    │   │                       ├── JavaVersion.java
    │   │                       ├── LagUtils.java
    │   │                       └── RollingOverHistory.java
    │   └── resources/
    │       ├── META-INF/
    │       │   └── services/
    │       │       └── org.slf4j.spi.SLF4JServiceProvider
    │       ├── config.yml
    │       ├── create.sql
    │       ├── default.jfc
    │       └── plugin.yml
    └── test/
        └── java/
            └── com/
                └── github/
                    └── games647/
                        └── lagmonitor/
                            ├── LagMonitorTest.java
                            ├── RollingOverHistoryTest.java
                            ├── listener/
                            │   └── BlockingConnectionSelectorTest.java
                            └── util/
                                ├── JavaVersionTest.java
                                └── LagUtilsTest.java
Download .txt
SYMBOL INDEX (510 symbols across 70 files)

FILE: src/main/java/com/github/games647/lagmonitor/LagMonitor.java
  class LagMonitor (line 53) | public class LagMonitor extends JavaPlugin {
    method onLoad (line 70) | @Override
    method onEnable (line 77) | @Override
    method setupMonitoringDatabase (line 142) | private void setupMonitoringDatabase() {
    method onDisable (line 168) | @Override
    method close (line 193) | private void close(Timer timer) {
    method getPageManager (line 200) | public PageManager getPageManager() {
    method getMonitorTimer (line 204) | public Timer getMonitorTimer() {
    method setMonitorTimer (line 208) | public void setMonitorTimer(Timer monitorTimer) {
    method getTrafficReader (line 212) | public TrafficReader getTrafficReader() {
    method getTpsHistoryTask (line 216) | public TPSHistoryTask getTpsHistoryTask() {
    method getPingManager (line 220) | public Optional<PingManager> getPingManager() {
    method getNativeData (line 224) | public NativeManager getNativeData() {
    method registerCommands (line 228) | private void registerCommands() {
    method formatDuration (line 258) | public static String formatDuration(Duration duration) {

FILE: src/main/java/com/github/games647/lagmonitor/MethodMeasurement.java
  class MethodMeasurement (line 10) | public class MethodMeasurement implements Comparable<MethodMeasurement> {
    method MethodMeasurement (line 19) | public MethodMeasurement(String id, String className, String method) {
    method getId (line 26) | public String getId() {
    method getClassName (line 30) | public String getClassName() {
    method getMethod (line 34) | public String getMethod() {
    method getTotalTime (line 38) | public long getTotalTime() {
    method getChildInvokes (line 42) | public Map<String, MethodMeasurement> getChildInvokes() {
    method getTimePercent (line 46) | public float getTimePercent(long parentTime) {
    method onMeasurement (line 51) | public void onMeasurement(StackTraceElement[] stackTrace, int skipElem...
    method writeString (line 69) | public void writeString(StringBuilder builder, int indent) {
    method equals (line 84) | @Override
    method hashCode (line 97) | @Override
    method compareTo (line 102) | @Override
    method toString (line 107) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/NativeManager.java
  class NativeManager (line 20) | public class NativeManager {
    method NativeManager (line 32) | public NativeManager(Logger logger, Path dataFolder) {
    method setupNativeAdapter (line 37) | public void setupNativeAdapter() {
    method getSystemInfo (line 50) | public Optional<SystemInfo> getSystemInfo() {
    method getProcessCPULoad (line 54) | public double getProcessCPULoad() {
    method getProcess (line 63) | public Optional<OSProcess> getProcess() {
    method getCPULoad (line 71) | public double getCPULoad() {
    method getOpenFileDescriptors (line 82) | public long getOpenFileDescriptors() {
    method getMaxFileDescriptors (line 92) | public long getMaxFileDescriptors() {
    method getTotalMemory (line 102) | public long getTotalMemory() {
    method getFreeMemory (line 113) | public long getFreeMemory() {
    method getFreeSwap (line 124) | public long getFreeSwap() {
    method getTotalSwap (line 136) | public long getTotalSwap() {
    method getFreeSpace (line 147) | public long getFreeSpace() {
    method getTotalSpace (line 159) | public long getTotalSpace() {

FILE: src/main/java/com/github/games647/lagmonitor/Pages.java
  class Pages (line 19) | public class Pages {
    method filterPackageNames (line 26) | public static String filterPackageNames(String packageName) {
    method Pages (line 47) | public Pages(String title, List<BaseComponent[]> lines) {
    method getTotalPages (line 52) | public int getTotalPages(boolean isPlayer) {
    method getAllLines (line 60) | public List<BaseComponent[]> getAllLines() {
    method getLastSentPage (line 64) | public int getLastSentPage() {
    method setLastSentPage (line 68) | public void setLastSentPage(int lastSentPage) {
    method getPage (line 72) | public List<BaseComponent[]> getPage(int page, boolean isPlayer) {
    method buildHeader (line 93) | public BaseComponent[] buildHeader(int page, int totalPages) {
    method buildFooter (line 111) | public String buildFooter(int page, boolean isPlayer) {
    method send (line 128) | public void send(CommandSender sender) {
    method send (line 132) | public void send(CommandSender sender, int page) {

FILE: src/main/java/com/github/games647/lagmonitor/command/EnvironmentCommand.java
  class EnvironmentCommand (line 22) | public class EnvironmentCommand extends LagCommand {
    method EnvironmentCommand (line 24) | public EnvironmentCommand(LagMonitor plugin) {
    method onCommand (line 28) | @Override
    method printExtendOsInfo (line 88) | private void printExtendOsInfo(CommandSender sender) {
    method displayDiskSpace (line 118) | private void displayDiskSpace(CommandSender sender) {

FILE: src/main/java/com/github/games647/lagmonitor/command/GraphCommand.java
  class GraphCommand (line 33) | public class GraphCommand extends LagCommand implements TabExecutor {
    method GraphCommand (line 39) | public GraphCommand(LagMonitor plugin) {
    method onCommand (line 48) | @Override
    method onTabComplete (line 84) | @Override
    method buildCombinedGraph (line 97) | private void buildCombinedGraph(Player player, String[] args) {
    method giveMap (line 118) | private void giveMap(Player player, MapView mapView) {
    method installRenderer (line 138) | private MapView installRenderer(Player player, GraphRenderer graphType) {

FILE: src/main/java/com/github/games647/lagmonitor/command/HelpCommand.java
  class HelpCommand (line 19) | public class HelpCommand extends LagCommand {
    method HelpCommand (line 23) | public HelpCommand(LagMonitor plugin) {
    method onCommand (line 27) | @Override
    method createCommandHelp (line 50) | private TextComponent createCommandHelp(String usage, String descripti...

FILE: src/main/java/com/github/games647/lagmonitor/command/LagCommand.java
  class LagCommand (line 19) | public abstract class LagCommand implements CommandExecutor {
    method LagCommand (line 29) | public LagCommand(LagMonitor plugin) {
    method isCommandAllowed (line 33) | private boolean isCommandAllowed(Command cmd, CommandSender sender) {
    method canExecute (line 53) | public boolean canExecute(CommandSender sender, Command cmd) {
    method sendMessage (line 62) | protected void sendMessage(CommandSender sender, String title, String ...
    method sendError (line 66) | protected void sendError(CommandSender sender, String msg) {
    method send (line 70) | public static void send(CommandSender sender, BaseComponent... compone...

FILE: src/main/java/com/github/games647/lagmonitor/command/MbeanCommand.java
  class MbeanCommand (line 25) | public class MbeanCommand extends LagCommand implements TabExecutor {
    method MbeanCommand (line 27) | public MbeanCommand(LagMonitor plugin) {
    method onCommand (line 31) | @Override
    method onTabComplete (line 70) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/command/MonitorCommand.java
  class MonitorCommand (line 26) | public class MonitorCommand extends LagCommand {
    method MonitorCommand (line 33) | public MonitorCommand(LagMonitor plugin) {
    method onCommand (line 37) | @Override
    method printTrace (line 75) | private void printTrace(List<BaseComponent[]> lines, long parentTime, ...
    method startMonitor (line 99) | private void startMonitor(CommandSender sender) {
    method stopMonitor (line 114) | private void stopMonitor(CommandSender sender) {
    method pasteMonitor (line 130) | private void pasteMonitor(final CommandSender sender) {

FILE: src/main/java/com/github/games647/lagmonitor/command/NativeCommand.java
  class NativeCommand (line 27) | public class NativeCommand extends LagCommand {
    method NativeCommand (line 29) | public NativeCommand(LagMonitor plugin) {
    method onCommand (line 33) | @Override
    method displayNativeInfo (line 49) | private void displayNativeInfo(CommandSender sender, SystemInfo system...
    method printRAMInfo (line 75) | private void printRAMInfo(CommandSender sender, List<PhysicalMemory> p...
    method printBoardInfo (line 86) | private void printBoardInfo(CommandSender sender, ComputerSystem compu...
    method printSensorsInfo (line 108) | private void printSensorsInfo(CommandSender sender, Sensors sensors) {
    method printDiskInfo (line 117) | private void printDiskInfo(CommandSender sender, List<HWDiskStore> dis...
    method displayMounts (line 132) | private void displayMounts(CommandSender sender, List<OSFileStore> fil...
    method printMountInfo (line 139) | private void printMountInfo(CommandSender sender, OSFileStore fileStor...

FILE: src/main/java/com/github/games647/lagmonitor/command/NetworkCommand.java
  class NetworkCommand (line 16) | public class NetworkCommand extends LagCommand {
    method NetworkCommand (line 18) | public NetworkCommand(LagMonitor plugin) {
    method onCommand (line 22) | @Override
    method displayNetworkInfo (line 38) | private void displayNetworkInfo(CommandSender sender, SystemInfo syste...
    method displayGlobalNetworkInfo (line 45) | private void displayGlobalNetworkInfo(CommandSender sender, NetworkPar...
    method displayInterfaceInfo (line 53) | private void displayInterfaceInfo(CommandSender sender, NetworkIF netw...

FILE: src/main/java/com/github/games647/lagmonitor/command/PaginationCommand.java
  class PaginationCommand (line 21) | public class PaginationCommand extends DumpCommand {
    method PaginationCommand (line 23) | public PaginationCommand(LagMonitor plugin) {
    method onCommand (line 27) | @Override
    method onPageNumber (line 64) | private void onPageNumber(String subCommand, CommandSender sender, Pag...
    method onNextPage (line 81) | private void onNextPage(Pages pagination, CommandSender sender) {
    method onPrevPage (line 91) | private void onPrevPage(Pages pagination, CommandSender sender) {
    method onSave (line 101) | private void onSave(Pages pagination, CommandSender sender) {
    method onShowAll (line 120) | private void onShowAll(Pages pagination, CommandSender sender) {

FILE: src/main/java/com/github/games647/lagmonitor/command/StackTraceCommand.java
  class StackTraceCommand (line 23) | public class StackTraceCommand extends LagCommand implements TabExecutor {
    method StackTraceCommand (line 27) | public StackTraceCommand(LagMonitor plugin) {
    method onCommand (line 31) | @Override
    method printStackTrace (line 60) | private void printStackTrace(CommandSender sender, StackTraceElement[]...
    method formatTraceElement (line 73) | private BaseComponent[] formatTraceElement(StackTraceElement traceElem...
    method onTabComplete (line 94) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/command/VmCommand.java
  class VmCommand (line 21) | public class VmCommand extends LagCommand {
    method VmCommand (line 23) | public VmCommand(LagMonitor plugin) {
    method onCommand (line 27) | @Override
    method displayCompilationInfo (line 53) | private void displayCompilationInfo(CommandSender sender, CompilationM...
    method displayRuntimeInfo (line 58) | private void displayRuntimeInfo(CommandSender sender, RuntimeMXBean ru...
    method displayCollectorStats (line 69) | private void displayCollectorStats(CommandSender sender, GarbageCollec...
    method displayClassLoading (line 75) | private void displayClassLoading(CommandSender sender, ClassLoadingMXB...
    method displayJavaVersion (line 81) | private void displayJavaVersion(CommandSender sender) {
    method formatJavaVersion (line 89) | private BaseComponent[] formatJavaVersion(JavaVersion version) {

FILE: src/main/java/com/github/games647/lagmonitor/command/dump/DumpCommand.java
  class DumpCommand (line 18) | public abstract class DumpCommand extends LagCommand {
    method DumpCommand (line 29) | public DumpCommand(LagMonitor plugin, String filePrefix, String fileEx...
    method getNewDumpFile (line 36) | public Path getNewDumpFile() {
    method invokeBeanCommand (line 42) | public Object invokeBeanCommand(String beanName, String command, Objec...
    method invokeDiagnosticCommand (line 50) | public String invokeDiagnosticCommand(String command, String... args)

FILE: src/main/java/com/github/games647/lagmonitor/command/dump/FlightCommand.java
  class FlightCommand (line 23) | public class FlightCommand extends DumpCommand {
    method FlightCommand (line 36) | public FlightCommand(LagMonitor plugin) {
    method areFlightMethodsAvailable (line 45) | private boolean areFlightMethodsAvailable() {
    method onCommand (line 58) | @Override
    method onStartCommand (line 98) | private void onStartCommand(CommandSender sender)
    method onStopCommand (line 104) | private void onStopCommand(CommandSender sender)
    method onDumpCommand (line 110) | private void onDumpCommand(CommandSender sender)

FILE: src/main/java/com/github/games647/lagmonitor/command/dump/HeapCommand.java
  class HeapCommand (line 22) | public class HeapCommand extends DumpCommand {
    method HeapCommand (line 29) | public HeapCommand(LagMonitor plugin) {
    method onCommand (line 33) | @Override
    method onDump (line 70) | private void onDump(CommandSender sender) {

FILE: src/main/java/com/github/games647/lagmonitor/command/dump/ThreadCommand.java
  class ThreadCommand (line 25) | public class ThreadCommand extends DumpCommand {
    method ThreadCommand (line 30) | public ThreadCommand(LagMonitor plugin) {
    method onCommand (line 34) | @Override
    method onDump (line 80) | private void onDump(CommandSender sender) {

FILE: src/main/java/com/github/games647/lagmonitor/command/minecraft/PingCommand.java
  class PingCommand (line 14) | public class PingCommand extends LagCommand {
    method PingCommand (line 16) | public PingCommand(LagMonitor plugin) {
    method onCommand (line 20) | @Override
    method displayPingSelf (line 37) | private void displayPingSelf(CommandSender sender) {
    method displayPingOther (line 51) | private void displayPingOther(CommandSender sender, Command command, S...
    method canSee (line 71) | private boolean canSee(CommandSender sender, String playerName) {

FILE: src/main/java/com/github/games647/lagmonitor/command/minecraft/SystemCommand.java
  class SystemCommand (line 29) | public class SystemCommand extends LagCommand {
    method SystemCommand (line 31) | public SystemCommand(LagMonitor plugin) {
    method onCommand (line 35) | @Override
    method displayUserInfo (line 49) | private void displayUserInfo(CommandSender sender) {
    method displayProcessInfo (line 59) | private void displayProcessInfo(CommandSender sender) {
    method displayRuntimeInfo (line 77) | private void displayRuntimeInfo(CommandSender sender, RuntimeMXBean ru...
    method displayThreadInfo (line 90) | private void displayThreadInfo(CommandSender sender, ThreadMXBean thre...
    method displayMemoryInfo (line 97) | private void displayMemoryInfo(CommandSender sender, Runtime runtime) {
    method displayMinecraftInfo (line 108) | private void displayMinecraftInfo(CommandSender sender) {
    method displayWorldInfo (line 131) | private void displayWorldInfo(CommandSender sender) {
    method getEnabledPlugins (line 160) | private int getEnabledPlugins(Plugin[] plugins) {

FILE: src/main/java/com/github/games647/lagmonitor/command/minecraft/TPSCommand.java
  class TPSCommand (line 18) | public class TPSCommand extends LagCommand {
    method TPSCommand (line 32) | public TPSCommand(LagMonitor plugin) {
    method onCommand (line 36) | @Override
    method printAverageHistory (line 65) | private void printAverageHistory(TPSHistoryTask tpsHistoryTask, Comman...
    method buildGraph (line 77) | private void buildGraph(float[] lastSeconds, int lastPos, List<StringB...
    method buildLine (line 91) | private void buildLine(float sampleSecond, List<StringBuilder> graphLi...

FILE: src/main/java/com/github/games647/lagmonitor/command/minecraft/TasksCommand.java
  class TasksCommand (line 22) | public class TasksCommand extends LagCommand {
    method TasksCommand (line 39) | public TasksCommand(LagMonitor plugin) {
    method onCommand (line 43) | @Override
    method formatTask (line 71) | private BaseComponent[] formatTask(BukkitTask pendingTask) {
    method getRunnableClass (line 90) | private Class<?> getRunnableClass(BukkitTask task) {

FILE: src/main/java/com/github/games647/lagmonitor/command/timing/PaperTimingsCommand.java
  class PaperTimingsCommand (line 51) | public class PaperTimingsCommand extends TimingCommand {
    method PaperTimingsCommand (line 65) | public PaperTimingsCommand(LagMonitor plugin) {
    method isTimingsEnabled (line 77) | @Override
    method sendTimings (line 82) | @Override
    method printTimings (line 102) | public void printTimings(Collection<BaseComponent[]> lines, TimingHist...
    method printChildren (line 152) | private void printChildren(Object parent, Object childData, Map<Intege...
    method printHeadData (line 175) | private void printHeadData(TimingHistory lastHistory, Collection<BaseC...

FILE: src/main/java/com/github/games647/lagmonitor/command/timing/SpigotTimingsCommand.java
  class SpigotTimingsCommand (line 32) | public class SpigotTimingsCommand extends TimingCommand {
    method SpigotTimingsCommand (line 37) | public SpigotTimingsCommand(LagMonitor plugin) {
    method isTimingsEnabled (line 41) | @Override
    method sendTimings (line 46) | @Override
    method sendParsedOutput (line 67) | private void sendParsedOutput(Iterable<CustomTimingsHandler> handlers,...
    method printHeadData (line 151) | private void printHeadData(long total, long activatedEntityTicks, long...
    method parseTimings (line 180) | private void parseTimings(Iterable<CustomTimingsHandler> handlers, Map...
    method getProperty (line 222) | private String getProperty(String line, String propertyName) {

FILE: src/main/java/com/github/games647/lagmonitor/command/timing/Timing.java
  class Timing (line 7) | public class Timing implements Comparable<Timing> {
    method Timing (line 16) | public Timing(String category) {
    method Timing (line 20) | public Timing(String category, long totalTime, long count) {
    method getCategoryName (line 26) | public String getCategoryName() {
    method getTotalTime (line 30) | public long getTotalTime() {
    method addTotal (line 34) | public void addTotal(long total) {
    method getTotalCount (line 38) | public long getTotalCount() {
    method addCount (line 42) | public void addCount(long count) {
    method calculateAverage (line 46) | public double calculateAverage() {
    method getSubCategories (line 54) | public Map<String, Timing> getSubCategories() {
    method addSubcategory (line 58) | public void addSubcategory(String name, long totalTime, long count) {
    method equals (line 69) | @Override
    method hashCode (line 80) | @Override
    method compareTo (line 85) | @Override
    method toString (line 90) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/command/timing/TimingCommand.java
  class TimingCommand (line 11) | public abstract class TimingCommand extends LagCommand {
    method TimingCommand (line 13) | public TimingCommand(LagMonitor plugin) {
    method onCommand (line 17) | @Override
    method sendTimings (line 33) | protected abstract void sendTimings(CommandSender sender);
    method isTimingsEnabled (line 35) | protected abstract boolean isTimingsEnabled();
    method highlightPct (line 37) | protected String highlightPct(float percent, int low, int med, int hig...

FILE: src/main/java/com/github/games647/lagmonitor/graph/ClassesGraph.java
  class ClassesGraph (line 8) | public class ClassesGraph extends GraphRenderer {
    method ClassesGraph (line 12) | public ClassesGraph() {
    method renderGraphTick (line 16) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/graph/CombinedGraph.java
  class CombinedGraph (line 5) | public class CombinedGraph extends GraphRenderer {
    method CombinedGraph (line 14) | public CombinedGraph(GraphRenderer... renderers) {
    method renderGraphTick (line 27) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/graph/CpuGraph.java
  class CpuGraph (line 9) | public class CpuGraph extends GraphRenderer {
    method CpuGraph (line 19) | public CpuGraph(Plugin plugin, NativeManager nativeData) {
    method renderGraphTick (line 26) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/graph/GraphRenderer.java
  class GraphRenderer (line 10) | public abstract class GraphRenderer extends MapRenderer {
    method GraphRenderer (line 29) | public GraphRenderer(String title) {
    method render (line 33) | @Override
    method renderGraphTick (line 68) | public abstract int renderGraphTick(MapCanvas canvas, int nextPosX);
    method getHeightScaled (line 70) | protected int getHeightScaled(int maxValue, int value) {
    method clearBar (line 74) | protected void clearBar(MapCanvas canvas, int posX) {
    method clearMap (line 81) | protected void clearMap(MapCanvas canvas) {
    method fillBar (line 87) | protected void fillBar(MapCanvas canvas, int xPos, int yStart, byte co...
    method drawText (line 93) | protected void drawText(MapCanvas canvas, int midX, int midY, String t...
    method getTextWidth (line 98) | private int getTextWidth(String text) {

FILE: src/main/java/com/github/games647/lagmonitor/graph/HeapGraph.java
  class HeapGraph (line 8) | public class HeapGraph extends GraphRenderer {
    method HeapGraph (line 12) | public HeapGraph() {
    method renderGraphTick (line 16) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/graph/ThreadsGraph.java
  class ThreadsGraph (line 8) | public class ThreadsGraph extends GraphRenderer {
    method ThreadsGraph (line 12) | public ThreadsGraph() {
    method renderGraphTick (line 16) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/listener/BlockingConnectionSelector.java
  class BlockingConnectionSelector (line 15) | public class BlockingConnectionSelector extends ProxySelector implements...
    method BlockingConnectionSelector (line 22) | public BlockingConnectionSelector(BlockingActionManager actionManager) {
    method select (line 26) | @Override
    method connectFailed (line 36) | @Override
    method inject (line 43) | @Override
    method restore (line 52) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/listener/GraphListener.java
  class GraphListener (line 24) | public class GraphListener implements Listener {
    method GraphListener (line 28) | public GraphListener() {
    method onInteract (line 41) | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
    method onDrop (line 58) | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
    method isOurGraph (line 67) | private boolean isOurGraph(ItemStack item) {
    method isOurGraphLegacy (line 86) | private boolean isOurGraphLegacy(ItemStack mapItem) {
    method isOurRenderer (line 95) | private boolean isOurRenderer(MapView mapView) {

FILE: src/main/java/com/github/games647/lagmonitor/listener/PageManager.java
  class PageManager (line 12) | public class PageManager implements Listener {
    method onPlayerQuit (line 16) | @EventHandler
    method getPagination (line 21) | public Pages getPagination(String username) {
    method setPagination (line 25) | public void setPagination(String username, Pages pagination) {

FILE: src/main/java/com/github/games647/lagmonitor/listener/ThreadSafetyListener.java
  class ThreadSafetyListener (line 33) | public class ThreadSafetyListener implements Listener {
    method ThreadSafetyListener (line 37) | public ThreadSafetyListener(BlockingActionManager actionManager) {
    method onCommand (line 41) | @EventHandler
    method onInventoryOpen (line 46) | @EventHandler
    method onPlayerMove (line 51) | @EventHandler
    method onPlayerTeleport (line 56) | @EventHandler
    method onPlayerJoin (line 61) | @EventHandler
    method onPlayerQuit (line 66) | @EventHandler
    method onItemHeldChange (line 71) | @EventHandler
    method onBlockPhysics (line 76) | @EventHandler
    method onBlockFromTo (line 81) | @EventHandler
    method onCreatureSpawn (line 86) | @EventHandler
    method onItemSpawn (line 91) | @EventHandler
    method onChunkLoad (line 96) | @EventHandler
    method onChunkUnload (line 101) | @EventHandler
    method onWorldLoad (line 106) | @EventHandler
    method onWorldSave (line 111) | @EventHandler
    method onWorldUnload (line 116) | @EventHandler
    method onPluginEnable (line 121) | @EventHandler
    method onPluginDisable (line 126) | @EventHandler
    method onSpawnChange (line 131) | @EventHandler
    method onSpawnChange (line 136) | @EventHandler
    method checkSafety (line 141) | private void checkSafety(Event eventType) {

FILE: src/main/java/com/github/games647/lagmonitor/logging/ForwardLogService.java
  class ForwardLogService (line 9) | public class ForwardLogService implements SLF4JServiceProvider {
    method getLoggerFactory (line 14) | public ILoggerFactory getLoggerFactory() {
    method getMarkerFactory (line 18) | public IMarkerFactory getMarkerFactory() {
    method getMDCAdapter (line 22) | public MDCAdapter getMDCAdapter() {
    method getRequesteApiVersion (line 26) | @Override
    method getRequestedApiVersion (line 31) | public String getRequestedApiVersion() {
    method initialize (line 35) | public void initialize() {

FILE: src/main/java/com/github/games647/lagmonitor/logging/ForwardingLoggerFactory.java
  class ForwardingLoggerFactory (line 12) | public class ForwardingLoggerFactory implements ILoggerFactory {
    method getLogger (line 18) | @Override
    method createJDKLogger (line 41) | protected static Logger createJDKLogger(java.util.logging.Logger parent)

FILE: src/main/java/com/github/games647/lagmonitor/ping/PaperPing.java
  class PaperPing (line 5) | public class PaperPing implements PingFetcher {
    method isAvailable (line 7) | @Override
    method getPing (line 18) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/ping/PingFetcher.java
  type PingFetcher (line 5) | public interface PingFetcher {
    method isAvailable (line 7) | boolean isAvailable();
    method getPing (line 9) | int getPing(Player player);

FILE: src/main/java/com/github/games647/lagmonitor/ping/ReflectionPing.java
  class ReflectionPing (line 16) | public class ReflectionPing implements PingFetcher {
    method isAvailable (line 46) | @Override
    method getPing (line 51) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/ping/SpigotPing.java
  class SpigotPing (line 5) | public class SpigotPing implements PingFetcher {
    method isAvailable (line 7) | @Override
    method getPing (line 18) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/storage/MonitorSaveTask.java
  class MonitorSaveTask (line 26) | public class MonitorSaveTask implements Runnable {
    method MonitorSaveTask (line 31) | public MonitorSaveTask(LagMonitor plugin, Storage storage) {
    method run (line 36) | @Override
    method getPlayerData (line 58) | private List<PlayerData> getPlayerData(final Map<UUID, WorldData> worl...
    method getWorldData (line 87) | private Map<UUID, WorldData> getWorldData()
    method save (line 116) | private int save() {

FILE: src/main/java/com/github/games647/lagmonitor/storage/NativeSaveTask.java
  class NativeSaveTask (line 20) | public class NativeSaveTask implements Runnable {
    method NativeSaveTask (line 34) | public NativeSaveTask(LagMonitor plugin, Storage storage) {
    method run (line 39) | @Override
    method getDifference (line 105) | private int getDifference(long newVal, long oldVal, long timeDiff) {

FILE: src/main/java/com/github/games647/lagmonitor/storage/PlayerData.java
  class PlayerData (line 5) | public class PlayerData {
    method PlayerData (line 12) | public PlayerData(int worldId, UUID uuid, String playerName, int ping) {
    method getWorldId (line 24) | public int getWorldId() {
    method getUuid (line 28) | public UUID getUuid() {
    method getPlayerName (line 32) | public String getPlayerName() {
    method getPing (line 36) | public int getPing() {
    method toString (line 40) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/storage/Storage.java
  class Storage (line 20) | public class Storage {
    method Storage (line 33) | public Storage(Logger logger, String host, int port, String database, ...
    type FeatureTester (line 52) | @FunctionalInterface
      method run (line 54) | void run(MysqlDataSource dataSource) throws SQLException;
    method tryFeature (line 57) | private void tryFeature(MysqlDataSource dataSource, FeatureTester task...
    method createTables (line 65) | public void createTables() throws SQLException {
    method saveMonitor (line 89) | public int saveMonitor(float procUsage, float osUsage, int freeRam, fl...
    method saveWorlds (line 118) | public boolean saveWorlds(int monitorId, Collection<WorldData> worldsD...
    method savePlayers (line 156) | public void savePlayers(Collection<PlayerData> playerData) {
    method saveNative (line 181) | public void saveNative(int mcRead, int mcWrite, int freeSpace, float f...
    method saveTps (line 207) | public void saveTps(float tps) {

FILE: src/main/java/com/github/games647/lagmonitor/storage/TPSSaveTask.java
  class TPSSaveTask (line 5) | public class TPSSaveTask implements Runnable {
    method TPSSaveTask (line 10) | public TPSSaveTask(TPSHistoryTask tpsHistoryTask, Storage storage) {
    method run (line 15) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/storage/WorldData.java
  class WorldData (line 6) | public class WorldData {
    method fromWorld (line 16) | public static WorldData fromWorld(World world) {
    method WorldData (line 29) | public WorldData(String worldName, int loadedChunks, int tileEntities,...
    method getWorldName (line 36) | public String getWorldName() {
    method getLoadedChunks (line 40) | public int getLoadedChunks() {
    method getTileEntities (line 44) | public int getTileEntities() {
    method getEntities (line 48) | public int getEntities() {
    method getWorldSize (line 52) | public int getWorldSize() {
    method setWorldSize (line 56) | public void setWorldSize(int worldSize) {
    method getRowId (line 60) | public int getRowId() {
    method setRowId (line 64) | public void setRowId(int rowId) {
    method toString (line 68) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/task/IODetectorTask.java
  class IODetectorTask (line 8) | public class IODetectorTask extends TimerTask {
    method IODetectorTask (line 13) | public IODetectorTask(BlockingActionManager actionManager, Thread main...
    method run (line 18) | @Override
    method isElementEqual (line 48) | private boolean isElementEqual(StackTraceElement traceElement, String ...

FILE: src/main/java/com/github/games647/lagmonitor/task/MonitorTask.java
  class MonitorTask (line 28) | public class MonitorTask extends TimerTask {
    method MonitorTask (line 40) | public MonitorTask(Logger logger, long threadId) {
    method getRootSample (line 45) | public synchronized MethodMeasurement getRootSample() {
    method getSamples (line 49) | public synchronized int getSamples() {
    method run (line 53) | @Override
    method paste (line 75) | public String paste() {
    method toString (line 108) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/task/PingManager.java
  class PingManager (line 23) | public class PingManager implements Runnable, Listener {
    method PingManager (line 34) | public PingManager(Plugin plugin) throws ReflectiveOperationException {
    method initializePingFetchur (line 39) | private PingFetcher initializePingFetchur()
    method run (line 53) | @Override
    method getHistory (line 64) | public RollingOverHistory getHistory(String playerName) {
    method addPlayer (line 68) | public void addPlayer(Player player) {
    method removePlayer (line 73) | public void removePlayer(Player player) {
    method onPlayerJoin (line 77) | @EventHandler
    method onPlayerQuit (line 87) | @EventHandler
    method clear (line 92) | public void clear() {

FILE: src/main/java/com/github/games647/lagmonitor/task/TPSHistoryTask.java
  class TPSHistoryTask (line 7) | public class TPSHistoryTask implements Runnable {
    method getMinuteSample (line 19) | public RollingOverHistory getMinuteSample() {
    method getQuarterSample (line 23) | public RollingOverHistory getQuarterSample() {
    method getHalfHourSample (line 27) | public RollingOverHistory getHalfHourSample() {
    method getLastSample (line 31) | public float getLastSample() {
    method run (line 38) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/threading/BlockingActionManager.java
  class BlockingActionManager (line 15) | public class BlockingActionManager implements Listener {
    method BlockingActionManager (line 42) | public BlockingActionManager(Plugin plugin) {
    method checkBlockingAction (line 46) | public void checkBlockingAction(String event) {
    method checkThreadSafety (line 54) | public void checkThreadSafety(String eventName) {
    method logCurrentStack (line 64) | public void logCurrentStack(String format, String eventName) {
    method findPlugin (line 96) | public Map.Entry<String, StackTraceElement> findPlugin(StackTraceEleme...

FILE: src/main/java/com/github/games647/lagmonitor/threading/BlockingSecurityManager.java
  class BlockingSecurityManager (line 9) | public class BlockingSecurityManager extends SecurityManager implements ...
    method BlockingSecurityManager (line 16) | public BlockingSecurityManager(BlockingActionManager actionManager) {
    method checkPermission (line 20) | @Override
    method checkPermission (line 29) | @Override
    method checkMainThreadOperation (line 38) | private void checkMainThreadOperation(Permission perm) {
    method isBlockingAction (line 44) | private boolean isBlockingAction(Permission permission) {
    method inject (line 51) | @Override
    method restore (line 60) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/threading/Injectable.java
  type Injectable (line 3) | public interface Injectable {
    method inject (line 5) | void inject();
    method restore (line 7) | void restore();

FILE: src/main/java/com/github/games647/lagmonitor/threading/PluginViolation.java
  class PluginViolation (line 5) | public class PluginViolation {
    method PluginViolation (line 14) | public PluginViolation(String pluginName, StackTraceElement stackTrace...
    method PluginViolation (line 23) | public PluginViolation(String event) {
    method getPluginName (line 32) | public String getPluginName() {
    method getSourceFile (line 36) | public String getSourceFile() {
    method getMethodName (line 40) | public String getMethodName() {
    method getLineNumber (line 44) | public int getLineNumber() {
    method getEvent (line 48) | public String getEvent() {
    method hashCode (line 52) | @Override
    method equals (line 57) | @Override
    method toString (line 69) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/traffic/CleanUpTask.java
  class CleanUpTask (line 8) | public class CleanUpTask implements Runnable {
    method CleanUpTask (line 13) | public CleanUpTask(ChannelPipeline pipeline, ChannelInboundHandlerAdap...
    method run (line 18) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/traffic/Reflection.java
  class Reflection (line 17) | public final class Reflection {
    type ConstructorInvoker (line 22) | @FunctionalInterface
      method invoke (line 31) | Object invoke(Object... arguments);
    type MethodInvoker (line 37) | @FunctionalInterface
      method invoke (line 47) | Object invoke(Object target, Object... arguments);
    type FieldAccessor (line 55) | public interface FieldAccessor<T> {
      method get (line 63) | T get(Object target);
      method set (line 71) | void set(Object target, Object value);
      method hasField (line 79) | boolean hasField(Object target);
    method Reflection (line 91) | private Reflection() {
    method getField (line 103) | public static <T> FieldAccessor<T> getField(Class<?> target, String na...
    method getField (line 115) | public static <T> FieldAccessor<T> getField(String className, String n...
    method getField (line 127) | public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> ...
    method getField (line 139) | public static <T> FieldAccessor<T> getField(String className, Class<T>...
    method getField (line 144) | private static <T> FieldAccessor<T> getField(Class<?> target, String n...
    method getMethod (line 197) | public static MethodInvoker getMethod(String className, String methodN...
    method getMethod (line 210) | public static MethodInvoker getMethod(Class<?> clazz, String methodNam...
    method getTypedMethod (line 224) | public static MethodInvoker getTypedMethod(Class<?> clazz, String meth...
    method getConstructor (line 255) | public static ConstructorInvoker getConstructor(String className, Clas...
    method getConstructor (line 267) | public static ConstructorInvoker getConstructor(Class<?> clazz, Class<...
    method getUntypedClass (line 295) | public static Class<Object> getUntypedClass(String lookupName) {
    method getClass (line 329) | public static Class<?> getClass(String lookupName) {
    method getMinecraftClass (line 339) | public static Class<?> getMinecraftClass(String name, String alias) {
    method getCraftBukkitClass (line 353) | public static Class<?> getCraftBukkitClass(String name) {
    method getCanonicalClass (line 363) | private static Class<?> getCanonicalClass(String canonicalName) {
    method expandVariables (line 377) | private static String expandVariables(String name) {

FILE: src/main/java/com/github/games647/lagmonitor/traffic/TinyProtocol.java
  class TinyProtocol (line 28) | public abstract class TinyProtocol {
    method TinyProtocol (line 51) | public TinyProtocol(final Plugin plugin) {
    method createServerChannelHandler (line 68) | private void createServerChannelHandler() {
    method onChannelRead (line 95) | public abstract void onChannelRead(ChannelHandlerContext handlerContex...
    method onChannelWrite (line 97) | public abstract void onChannelWrite(ChannelHandlerContext handlerConte...
    method registerChannelHandler (line 99) | @SuppressWarnings("unchecked")
    method unregisterChannelHandler (line 126) | private void unregisterChannelHandler() {
    method close (line 141) | public final void close() {

FILE: src/main/java/com/github/games647/lagmonitor/traffic/TrafficReader.java
  class TrafficReader (line 12) | public class TrafficReader extends TinyProtocol {
    method TrafficReader (line 17) | public TrafficReader(Plugin plugin) {
    method getIncomingBytes (line 21) | public LongAdder getIncomingBytes() {
    method getOutgoingBytes (line 25) | public LongAdder getOutgoingBytes() {
    method onChannelRead (line 29) | @Override
    method onChannelWrite (line 34) | @Override
    method onChannel (line 39) | private void onChannel(Object object, boolean incoming) {

FILE: src/main/java/com/github/games647/lagmonitor/util/JavaVersion.java
  class JavaVersion (line 10) | public class JavaVersion implements Comparable<JavaVersion> {
    method JavaVersion (line 23) | protected JavaVersion(String raw, int major, int minor, int security, ...
    method JavaVersion (line 31) | public JavaVersion(String version) {
    method detect (line 56) | public static JavaVersion detect() {
    method getRaw (line 60) | public String getRaw() {
    method getMajor (line 64) | public int getMajor() {
    method getMinor (line 68) | public int getMinor() {
    method getSecurity (line 72) | public int getSecurity() {
    method isPreRelease (line 76) | public boolean isPreRelease() {
    method isOutdated (line 80) | public boolean isOutdated() {
    method compareTo (line 84) | @Override
    method equals (line 94) | @Override
    method hashCode (line 105) | @Override
    method toString (line 110) | @Override

FILE: src/main/java/com/github/games647/lagmonitor/util/LagUtils.java
  class LagUtils (line 16) | public class LagUtils {
    method LagUtils (line 18) | private LagUtils() {
    method byteToMega (line 21) | public static int byteToMega(long bytes) {
    method round (line 25) | public static float round(double number) {
    method round (line 29) | public static float round(double value, int places) {
    method isFilledMapSupported (line 39) | public static boolean isFilledMapSupported() {
    method readableBytes (line 43) | public static String readableBytes(long bytes) {
    method getFolderSize (line 55) | public static long getFolderSize(Logger logger, Path folder) {

FILE: src/main/java/com/github/games647/lagmonitor/util/RollingOverHistory.java
  class RollingOverHistory (line 5) | public class RollingOverHistory {
    method RollingOverHistory (line 13) | public RollingOverHistory(int size, float firstValue) {
    method add (line 18) | public void add(float sample) {
    method getAverage (line 37) | public float getAverage() {
    method getCurrentPosition (line 41) | public int getCurrentPosition() {
    method getCurrentSize (line 45) | public int getCurrentSize() {
    method getLastSample (line 49) | public float getLastSample() {
    method getSamples (line 58) | public float[] getSamples() {
    method reset (line 62) | public void reset(float firstVal) {
    method toString (line 67) | @Override

FILE: src/main/resources/create.sql
  type `{prefix}tps` (line 6) | CREATE TABLE IF NOT EXISTS `{prefix}tps`
  type `{prefix}monitor` (line 13) | CREATE TABLE IF NOT EXISTS `{prefix}monitor`
  type `{prefix}worlds` (line 26) | CREATE TABLE IF NOT EXISTS `{prefix}worlds`
  type `{prefix}players` (line 38) | CREATE TABLE IF NOT EXISTS `{prefix}players`
  type `{prefix}native` (line 48) | CREATE TABLE IF NOT EXISTS `{prefix}native`

FILE: src/test/java/com/github/games647/lagmonitor/LagMonitorTest.java
  class LagMonitorTest (line 9) | public class LagMonitorTest {
    method testEmptyDuration (line 11) | @Test
    method testOverYearDuration (line 16) | @Test
    method testValidSecondDuration (line 22) | @Test
    method testOverSecondDuration (line 28) | @Test
    method testFormattingCombined (line 34) | @Test

FILE: src/test/java/com/github/games647/lagmonitor/RollingOverHistoryTest.java
  class RollingOverHistoryTest (line 10) | public class RollingOverHistoryTest {
    method testGetAverage (line 12) | @Test
    method testGetCurrentPosition (line 25) | @Test
    method testGetLastSample (line 38) | @Test
    method testGetSamples (line 51) | @Test

FILE: src/test/java/com/github/games647/lagmonitor/listener/BlockingConnectionSelectorTest.java
  class BlockingConnectionSelectorTest (line 18) | @ExtendWith(MockitoExtension.class)
    method setUp (line 25) | @BeforeEach
    method testHttp (line 30) | @Test
    method testDuplicateHttp (line 36) | @Test
    method testBlockingSocket (line 44) | @Test

FILE: src/test/java/com/github/games647/lagmonitor/util/JavaVersionTest.java
  class JavaVersionTest (line 10) | public class JavaVersionTest {
    method detectDeveloperVersion (line 12) | @Test
    method parseJava8 (line 17) | @Test
    method parseJava9 (line 26) | @Test
    method parseJava9EarlyAccess (line 35) | @Test
    method parseJava9WithVendorSuffix (line 45) | @Test
    method parseJava14 (line 54) | @Test
    method parseJava10Internal (line 63) | @Test
    method comparePreRelease (line 73) | @Test
    method compareMinor (line 80) | @Test
    method compareMajor (line 87) | @Test
    method compareEqual (line 94) | @Test

FILE: src/test/java/com/github/games647/lagmonitor/util/LagUtilsTest.java
  class LagUtilsTest (line 9) | public class LagUtilsTest {
    method byteToMega (line 11) | @Test
    method readableBytes (line 17) | @Test
Condensed preview — 81 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (329K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 1171,
    "preview": "version: 2\nupdates:\n- package-ecosystem: maven\n  directory: \"/\"\n  schedule:\n    interval: monthly\n  open-pull-requests-l"
  },
  {
    "path": ".gitignore",
    "chars": 552,
    "preview": "# Eclipse\n.classpath\n.project\n.settings/\n\n# NetBeans\nnbproject/\nnb-configuration.xml\n\n# IntelliJ\n*.iml\n*.ipr\n*.iws\n.idea"
  },
  {
    "path": ".travis.yml",
    "chars": 271,
    "preview": "# Use https://travis-ci.org/ for automatic testing\n\n# speed up testing https://blog.travis-ci.com/2014-12-17-faster-buil"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 8290,
    "preview": "# Changelog\n\n## 1.17.1\n\n* Make the link for monitoring clickable\n* Improve performance of /tasks command using MethodHan"
  },
  {
    "path": "LICENSE",
    "chars": 1072,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2018\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 5551,
    "preview": "# LagMonitor\n\n## Description\n\nGives you the possibility to monitor your server performance. This plugin is based on the "
  },
  {
    "path": "pom.xml",
    "chars": 8944,
    "preview": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:sc"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/LagMonitor.java",
    "chars": 11045,
    "preview": "package com.github.games647.lagmonitor;\n\nimport com.github.games647.lagmonitor.command.EnvironmentCommand;\nimport com.gi"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/MethodMeasurement.java",
    "chars": 3776,
    "preview": "package com.github.games647.lagmonitor;\n\nimport com.google.common.collect.ImmutableMap;\n\nimport java.util.HashMap;\nimpor"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/NativeManager.java",
    "chars": 5760,
    "preview": "package com.github.games647.lagmonitor;\n\nimport com.sun.management.UnixOperatingSystemMXBean;\n\nimport java.io.IOExceptio"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/Pages.java",
    "chars": 5777,
    "preview": "package com.github.games647.lagmonitor;\n\nimport java.time.LocalTime;\nimport java.time.format.DateTimeFormatter;\nimport j"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/EnvironmentCommand.java",
    "chars": 5260,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.gam"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/GraphCommand.java",
    "chars": 5006,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.gam"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/HelpCommand.java",
    "chars": 3004,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\n\nimport java.util.Map"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/LagCommand.java",
    "chars": 2607,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\n\nimport java.util.Arr"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/MbeanCommand.java",
    "chars": 3692,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\n\nimport java.lang.man"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/MonitorCommand.java",
    "chars": 5370,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.gam"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/NativeCommand.java",
    "chars": 6495,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.gam"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/NetworkCommand.java",
    "chars": 2786,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.gam"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/PaginationCommand.java",
    "chars": 4811,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.gam"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/StackTraceCommand.java",
    "chars": 3948,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.gam"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/VmCommand.java",
    "chars": 4734,
    "preview": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.gam"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/dump/DumpCommand.java",
    "chars": 2355,
    "preview": "package com.github.games647.lagmonitor.command.dump;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.githu"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/dump/FlightCommand.java",
    "chars": 4311,
    "preview": "package com.github.games647.lagmonitor.command.dump;\n\nimport com.github.games647.lagmonitor.LagMonitor;\n\nimport java.lan"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/dump/HeapCommand.java",
    "chars": 3217,
    "preview": "package com.github.games647.lagmonitor.command.dump;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.githu"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/dump/ThreadCommand.java",
    "chars": 3473,
    "preview": "package com.github.games647.lagmonitor.command.dump;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.githu"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/minecraft/PingCommand.java",
    "chars": 2932,
    "preview": "package com.github.games647.lagmonitor.command.minecraft;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com."
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/minecraft/SystemCommand.java",
    "chars": 6931,
    "preview": "package com.github.games647.lagmonitor.command.minecraft;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com."
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/minecraft/TPSCommand.java",
    "chars": 4559,
    "preview": "package com.github.games647.lagmonitor.command.minecraft;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com."
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/minecraft/TasksCommand.java",
    "chars": 3230,
    "preview": "package com.github.games647.lagmonitor.command.minecraft;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com."
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/timing/PaperTimingsCommand.java",
    "chars": 9086,
    "preview": "package com.github.games647.lagmonitor.command.timing;\n\nimport co.aikar.timings.TimingHistory;\nimport co.aikar.timings.T"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/timing/SpigotTimingsCommand.java",
    "chars": 10448,
    "preview": "package com.github.games647.lagmonitor.command.timing;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.git"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/timing/Timing.java",
    "chars": 2561,
    "preview": "package com.github.games647.lagmonitor.command.timing;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/timing/TimingCommand.java",
    "chars": 1439,
    "preview": "package com.github.games647.lagmonitor.command.timing;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.git"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/ClassesGraph.java",
    "chars": 866,
    "preview": "package com.github.games647.lagmonitor.graph;\n\nimport java.lang.management.ClassLoadingMXBean;\nimport java.lang.manageme"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/CombinedGraph.java",
    "chars": 1529,
    "preview": "package com.github.games647.lagmonitor.graph;\n\nimport org.bukkit.map.MapCanvas;\n\npublic class CombinedGraph extends Grap"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/CpuGraph.java",
    "chars": 1691,
    "preview": "package com.github.games647.lagmonitor.graph;\n\nimport com.github.games647.lagmonitor.NativeManager;\n\nimport org.bukkit.B"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/GraphRenderer.java",
    "chars": 3295,
    "preview": "package com.github.games647.lagmonitor.graph;\n\nimport org.bukkit.entity.Player;\nimport org.bukkit.map.MapCanvas;\nimport "
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/HeapGraph.java",
    "chars": 1202,
    "preview": "package com.github.games647.lagmonitor.graph;\n\nimport java.lang.management.ManagementFactory;\nimport java.lang.managemen"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/ThreadsGraph.java",
    "chars": 1064,
    "preview": "package com.github.games647.lagmonitor.graph;\n\nimport java.lang.management.ManagementFactory;\nimport java.lang.managemen"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/listener/BlockingConnectionSelector.java",
    "chars": 1852,
    "preview": "package com.github.games647.lagmonitor.listener;\n\nimport com.github.games647.lagmonitor.threading.BlockingActionManager;"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/listener/GraphListener.java",
    "chars": 3203,
    "preview": "package com.github.games647.lagmonitor.listener;\n\nimport com.github.games647.lagmonitor.graph.GraphRenderer;\nimport com."
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/listener/PageManager.java",
    "chars": 724,
    "preview": "package com.github.games647.lagmonitor.listener;\n\nimport com.github.games647.lagmonitor.Pages;\n\nimport java.util.HashMap"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/listener/ThreadSafetyListener.java",
    "chars": 4442,
    "preview": "package com.github.games647.lagmonitor.listener;\n\nimport com.github.games647.lagmonitor.threading.BlockingActionManager;"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/logging/ForwardLogService.java",
    "chars": 1024,
    "preview": "package com.github.games647.lagmonitor.logging;\n\nimport org.slf4j.ILoggerFactory;\nimport org.slf4j.IMarkerFactory;\nimpor"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/logging/ForwardingLoggerFactory.java",
    "chars": 1777,
    "preview": "package com.github.games647.lagmonitor.logging;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Invocati"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/ping/PaperPing.java",
    "chars": 521,
    "preview": "package com.github.games647.lagmonitor.ping;\n\nimport org.bukkit.entity.Player;\n\npublic class PaperPing implements PingFe"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/ping/PingFetcher.java",
    "chars": 174,
    "preview": "package com.github.games647.lagmonitor.ping;\n\nimport org.bukkit.entity.Player;\n\npublic interface PingFetcher {\n\n    bool"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/ping/ReflectionPing.java",
    "chars": 2337,
    "preview": "package com.github.games647.lagmonitor.ping;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games6"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/ping/SpigotPing.java",
    "chars": 506,
    "preview": "package com.github.games647.lagmonitor.ping;\n\nimport org.bukkit.entity.Player;\n\npublic class SpigotPing implements PingF"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/MonitorSaveTask.java",
    "chars": 5509,
    "preview": "package com.github.games647.lagmonitor.storage;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.gam"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/NativeSaveTask.java",
    "chars": 3979,
    "preview": "package com.github.games647.lagmonitor.storage;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.gam"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/PlayerData.java",
    "chars": 1058,
    "preview": "package com.github.games647.lagmonitor.storage;\n\nimport java.util.UUID;\n\npublic class PlayerData {\n\n    private final in"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/Storage.java",
    "chars": 8644,
    "preview": "package com.github.games647.lagmonitor.storage;\n\nimport com.google.common.collect.Lists;\nimport com.mysql.cj.jdbc.MysqlD"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/TPSSaveTask.java",
    "chars": 605,
    "preview": "package com.github.games647.lagmonitor.storage;\n\nimport com.github.games647.lagmonitor.task.TPSHistoryTask;\n\npublic clas"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/WorldData.java",
    "chars": 1975,
    "preview": "package com.github.games647.lagmonitor.storage;\n\nimport org.bukkit.Chunk;\nimport org.bukkit.World;\n\npublic class WorldDa"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/task/IODetectorTask.java",
    "chars": 2698,
    "preview": "package com.github.games647.lagmonitor.task;\n\nimport com.github.games647.lagmonitor.threading.BlockingActionManager;\n\nim"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/task/MonitorTask.java",
    "chars": 4114,
    "preview": "package com.github.games647.lagmonitor.task;\n\nimport com.github.games647.lagmonitor.MethodMeasurement;\nimport com.github"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/task/PingManager.java",
    "chars": 3285,
    "preview": "package com.github.games647.lagmonitor.task;\n\nimport com.github.games647.lagmonitor.ping.PaperPing;\nimport com.github.ga"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/task/TPSHistoryTask.java",
    "chars": 1833,
    "preview": "package com.github.games647.lagmonitor.task;\n\nimport com.github.games647.lagmonitor.util.RollingOverHistory;\n\nimport jav"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/threading/BlockingActionManager.java",
    "chars": 5353,
    "preview": "package com.github.games647.lagmonitor.threading;\n\nimport com.google.common.collect.Maps;\nimport com.google.common.colle"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/threading/BlockingSecurityManager.java",
    "chars": 1942,
    "preview": "package com.github.games647.lagmonitor.threading;\n\nimport com.google.common.collect.ImmutableSet;\n\nimport java.io.FilePe"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/threading/Injectable.java",
    "chars": 124,
    "preview": "package com.github.games647.lagmonitor.threading;\n\npublic interface Injectable {\n\n    void inject();\n\n    void restore()"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/threading/PluginViolation.java",
    "chars": 2089,
    "preview": "package com.github.games647.lagmonitor.threading;\n\nimport java.util.Objects;\n\npublic class PluginViolation {\n\n    privat"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/traffic/CleanUpTask.java",
    "chars": 743,
    "preview": "package com.github.games647.lagmonitor.traffic;\n\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.c"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/traffic/Reflection.java",
    "chars": 15079,
    "preview": "package com.github.games647.lagmonitor.traffic;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\ni"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/traffic/TinyProtocol.java",
    "chars": 5646,
    "preview": "package com.github.games647.lagmonitor.traffic;\n\nimport com.github.games647.lagmonitor.traffic.Reflection.FieldAccessor;"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/traffic/TrafficReader.java",
    "chars": 1555,
    "preview": "package com.github.games647.lagmonitor.traffic;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufHolder;\ni"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/util/JavaVersion.java",
    "chars": 3780,
    "preview": "package com.github.games647.lagmonitor.util;\n\nimport com.google.common.collect.ComparisonChain;\n\nimport java.util.Object"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/util/LagUtils.java",
    "chars": 2199,
    "preview": "package com.github.games647.lagmonitor.util;\n\nimport com.google.common.base.Enums;\n\nimport java.io.IOException;\nimport j"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/util/RollingOverHistory.java",
    "chars": 1824,
    "preview": "package com.github.games647.lagmonitor.util;\n\nimport java.util.Arrays;\n\npublic class RollingOverHistory {\n\n    private f"
  },
  {
    "path": "src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider",
    "chars": 57,
    "preview": "com.github.games647.lagmonitor.logging.ForwardLogService\n"
  },
  {
    "path": "src/main/resources/config.yml",
    "chars": 3457,
    "preview": "# ${project.name} main config\n\n# If this option is enabled, this plugin will check for events which should run on the ma"
  },
  {
    "path": "src/main/resources/create.sql",
    "chars": 2291,
    "preview": "# LagMonitor table\n# Add Ids to each table, because it would be easier to refer to those entries in a lightweight way\n# "
  },
  {
    "path": "src/main/resources/default.jfc",
    "chars": 35581,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n     Recommended way to edit .jfc files is to use the configure command of\n "
  },
  {
    "path": "src/main/resources/plugin.yml",
    "chars": 5232,
    "preview": "# project data for Bukkit in order to register our plugin with all it components\n# ${-} are variables from Maven (pom.xm"
  },
  {
    "path": "src/test/java/com/github/games647/lagmonitor/LagMonitorTest.java",
    "chars": 1246,
    "preview": "package com.github.games647.lagmonitor;\n\nimport java.time.Duration;\n\nimport org.junit.jupiter.api.Test;\n\nimport static o"
  },
  {
    "path": "src/test/java/com/github/games647/lagmonitor/RollingOverHistoryTest.java",
    "chars": 1690,
    "preview": "package com.github.games647.lagmonitor;\n\nimport com.github.games647.lagmonitor.util.RollingOverHistory;\n\nimport org.juni"
  },
  {
    "path": "src/test/java/com/github/games647/lagmonitor/listener/BlockingConnectionSelectorTest.java",
    "chars": 1582,
    "preview": "package com.github.games647.lagmonitor.listener;\n\nimport com.github.games647.lagmonitor.threading.BlockingActionManager;"
  },
  {
    "path": "src/test/java/com/github/games647/lagmonitor/util/JavaVersionTest.java",
    "chars": 3155,
    "preview": "package com.github.games647.lagmonitor.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.As"
  },
  {
    "path": "src/test/java/com/github/games647/lagmonitor/util/LagUtilsTest.java",
    "chars": 735,
    "preview": "package com.github.games647.lagmonitor.util;\n\nimport java.util.Locale;\n\nimport org.junit.jupiter.api.Test;\n\nimport stati"
  }
]

About this extraction

This page contains the full source code of the games647/LagMonitor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 81 files (302.5 KB), approximately 70.0k tokens, and a symbol index with 510 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!