[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: maven\n  directory: \"/\"\n  schedule:\n    interval: monthly\n  open-pull-requests-limit: 10\n  ignore:\n  - dependency-name: com.github.oshi:oshi-demo\n    versions:\n    - \"> 5.2.2, < 5.3\"\n  - dependency-name: com.github.oshi:oshi-demo\n    versions:\n    - \"> 5.3.4, < 5.4\"\n  - dependency-name: io.netty:netty-codec\n    versions:\n    - \"> 4.1.45.Final\"\n  - dependency-name: mysql:mysql-connector-java\n    versions:\n    - \"> 5.1.48\"\n  - dependency-name: org.apache.maven.plugins:maven-shade-plugin\n    versions:\n    - \"> 3.2.3, < 3.3\"\n  - dependency-name: org.apache.maven.plugins:maven-surefire-plugin\n    versions:\n    - \"> 2.22.0, < 2.23\"\n  - dependency-name: org.mockito:mockito-junit-jupiter\n    versions:\n    - \"> 3.4.4, < 3.5\"\n  - dependency-name: org.mockito:mockito-junit-jupiter\n    versions:\n    - \">= 3.5.a, < 3.6\"\n  - dependency-name: pl.project13.maven:git-commit-id-plugin\n    versions:\n    - \"> 4.0.0, < 4.1\"\n  - dependency-name: com.github.oshi:oshi-demo\n    versions:\n    - 5.4.1\n    - 5.5.0\n    - 5.5.1\n    - 5.6.1\n    - 5.7.0\n  - dependency-name: org.mockito:mockito-junit-jupiter\n    versions:\n    - 3.7.7\n    - 3.8.0\n"
  },
  {
    "path": ".gitignore",
    "content": "# 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/\n\n# Maven\ntarget/\npom.xml.versionsBackup\n\n# Gradle\n.gradle\n\n# Ignore Gradle GUI config\ngradle-app.setting\n\n# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)\n!gradle-wrapper.jar\n\n# various other potential build files\nbuild/\nbin/\ndist/\nmanifest.mf\n*.log\n\n# Vim\n.*.sw[a-p]\n\n# virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n\n# Mac filesystem dust\n.DS_Store\n\n"
  },
  {
    "path": ".travis.yml",
    "content": "# Use https://travis-ci.org/ for automatic testing\n\n# speed up testing https://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/\nsudo: false\n\n# This is a java project\nlanguage: java\n\nscript: mvn test -B\n\njdk:\n  - oraclejdk8\n  - oraclejdk9\n\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 1.17.1\n\n* Make the link for monitoring clickable\n* Improve performance of /tasks command using MethodHandles\n* Fail safely on native library errors\n\n## 1.17\n\n* Use faster MethodHandles to lookup the player ping\n* Close all resources after calculating folder size\n* Verify if JFR methods are available in the current VM\n* Enable native driver by default if available\n* Hide vanished players from the ping command\n* Merge Paper and Spigot Timings parsing into one command\n* Improve wording for thread block or safety warnings:\n    * If you think something is missing or could be described better, please make a pull request.\n* Add Thread safety checks for command events too\n    * Ref: https://www.spigotmc.org/threads/plugins-triggering-commands-async.31815/\n* Add statically compiled java version checker\n* Add a lot of more native Hardware and Software details:\n    * Sensors (voltage, fan speed)\n    * Motherboard\n    * Networking\n    * CPU\n    * Java properties\n    * Process\n    * User\n* Replace outdated sigar library with oshi\n* Validate input for average comparison (Fixes #37)\n\n## 1.16\n\n* Use Bukkit's internal method to find the plugin owner\n* Fix checking vanilla command class check if we found an obfuscated plugin\n* Dynamically adjust text padding for graphs\n* Fix invalid threads graph name\n* Count the read/write of all disks\n* Use migration file creating MySQL table\n* Use MEDIUMINT for os with > 64GB of ram (Related #33)\n* Fix folder size calculation\n* Fix free ram calculation (Fixes #33)\n* Delay ping fetching on player join, because the first ping request is very inaccurate.\n\n## 1.15\n\n* Better url output for blocking http actions\n* Query the partition and not the filesystem for the reads/writes\n* Add linux distribution info\n* Fix total file system space\n\n## 1.14.3\n\n* Refactor plugin detection. Now it skips the first x entries of LagMonitor until it finds another class loader.\n\n## 1.14.2\n\n* Fix plugin name detection\n\n## 1.14.1\n\n* Fix 1.12 support\n\n## 1.14\n\n* Show file system type for the native command\n* Replace the /paper command alias with /paper-timing to prevent overrides by Paper itself\n\n## 1.13\n\n* Whitelist vanilla commands\n\n## 1.12\n\n* Filter invalid ping values\n* Migrate to Java 7 Path API for faster free space and other file system lookups\n\n## 1.11.10\n\n* Better block message descriptions\n\n## 1.11.9\n\n* Fix parsing hover event for 1.8 clients\n* Wrap to a new line only after the word\n* Use .spigot() for sendMessage(BaseComponent) for backwards compatibility\n\n## 1.11.8\n\n* Fix map listener for older minecraft version (with only one item-hand)\n\n## 1.11.7\n\n* Removed old debug code\n* Fix variable replacing in the help command\n\n## 1.11.6\n\n* Fixed memory leak for player pings on player quit\n\n## 1.11.5\n\n* Added a help page\n* Added new permission lagmonitor.command.help\n\n## 1.11.4\n\n* Fix users don't receive a map on graph command\n* Display error message for untracked ping players\n* Fail silently if the jfc file already exists\n\n## 1.11.3\n\n* Fix detecting socket connections (socket-block-detection) if the default proxy is null\n\n## 1.11.2\n\n* Optimize plugin violations handling\n* Fix security manager spams if enabled\n* Fix log caused methods only once even if it's disabled\n\n## 1.11.1\n\n* Add missing uri to the connection selector\n* Fix plugin name detection and thread-safety (Fixes #17)\n\n## 1.11\n\n* Added sigar as fallback when Oracle API isn't available (com.sun.management.OperatingSystemMXBean)\n\n## 1.10.1\n\n* Fix thread safety check\n\n## 1.10\n\n* Add hideStacktrace config property, which shows only two lines\n* Add oncePerPlugin config property which report it only one time per startup and plugin\n* Add a way to find the plugin source. [Experimental]\n\n## 1.9.1\n\n* Allow blocking actions on server startup (Fixes #15)\n* Clarify blocking action message\n* Upgrade to Java 8 (requires now Java 8)\n\n## 1.9\n\n* Add monitor pastes to https://paste.enginehub.org/ - Please support for this awesome service and please do not spam it\n* Fix showing duplicate http blocking messages, because a http connection is also a socket connection\n* Fix showing stacktrace on blocking action\n\n## 1.8\n\n* Add /lagpage < save >  and /lagpage < all >\n\n## 1.7.2\n\n* Fix traffic reader storage save\n* Warn users who still use the outdated Java 7 to upgrade to a newer version\n\n## 1.7\n\n* Fail safely on an error for traffic reader\n* Add configurable table prefix\n* Add debug code if the storage insert failed\n\n## 1.6\n\n* Added whitelist for certain commands for specific users\n\n## 1.5\n\n* Added a faster and less error-prone blocking http detection\n\n## 1.4\n\n* Added monitoring to a MySQL database\n* Added a unsupported java vendor hint to heap and thread dumps\n* Speed up the native command by loading the native driver only on plugin load\n\n## 1.3.2\n\n* Fix command permission for /ping player\n\n## 1.3.1\n\n* Fix class not found in paper spigot timings parser if user is using normal spigot\n\n## 1.3\n\n* Added PaperTimings head data\n* Added percent values to the paper spigot timings\n* Fixed combined plugin name\n* Fixed unknown entries in paper spigot timings parser\n* Fixed missing total second head data in spigot timings parser\n* Fixed pagination error from the last page\n\n## 1.2\n\n* Added support for Java Flight Recorder dump\n* Added default configuration file for flight recorder\n* Fixed permission of lagpage command has the paper command permission\n\n## 1.1\n\n* Added thread dump to file option /thread dump\n* Added heap dump to file option /heap dump\n* Fix pagination error if the user is requesting a too high page number\n\n## 1.0\n\n* Added plugin injection (commands, listener and tasks)\n* Added pagination\n* Added /heap command for heap dumps\n* Added world size to the system command\n* Added tile entities count to the system command\n* Added security manager for more efficient blocking checks\n* Added combined graphs example: /graph cpu heap threads\n* Added check if timings is enabled for Paper servers\n* Improved performance of commands by caching them with the pagination\n* Optimize Spigot timings parser\n\n## 0.7\n\n* Added /lag alias for the /tpshistory command\n* Added swap to the environment command\n* Added tasks command\n* Added /vm command for class loading, garbage collectors, vm specifications\n* Added basic Paper timings parser\n* Added load average to the environment command\n* Moved Java version to the vm command\n* Optimized thread locking in monitor/profiler for better performance\n\n## 0.6\n\n* Added /native command to query native data like OS uptime, Network adapter speed, CPU MHZ, ...\n* Added startup parameters to the system command\n* Added thread-safety check\n* Added blocking, waiting, sleeping check\n* Added Thread id to the threads command\n* Improved readability for tpshistory command in console\n* Fixed very low tps value displayed as full tps\n* Fixed scrolling tpsHistory\n* Fixed NPE on plugin load at runtime\n* Fixes ClassNotFoundException on reload if traffic reader is activated\n* Fixed rounding issues for the average ping\n* Fixed cleanup of monitor task on plugin disable\n\n## 0.5\n\n* Added Ping History -> displays average ping now\n* Added traffic counter\n* Added config\n* Reduce memory usage by getting the stacktrace of only one thread\n* Fixed thread safety\n\n## 0.4\n\n* Added world info to the system command\n* Added lazy loading for thread monitor to reduce memory usage\n* Added worlds, players and plugins count to the system command\n* Added samples count for thread monitor\n* Improved tons of command styling\n* Fixed thread safety\n* Fixed free memory value\n* Fixed memory leak for thread monitor\n* Fixed ping method only displaying the own ping\n\n## 0.3\n\n* Fixed: max memory output in the /system command\n* Added color highlighting for performance intensive tasks in the timings report\n* Fixed timings output\n* Added warning if timings are deactivated\n* Added classes graph\n* Added command completion for all commands\n* Updated to Minecraft 1.9\n* Added missing permission node for /ping [player] to the plugin.yml\n\n## 0.2\n\n* Added environment command\n* Added server version to the system command\n* Added more graphs (Threads, CPU usage)\n* Fixed CPU usage value\n* Improved Command output styling\n* Reduced delay start of ticks per second task\n\n## 0.1.1\n\n* Added command permissions\n* Added online check for the ping command\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016-2018\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# LagMonitor\n\n## Description\n\nGives you the possibility to monitor your server performance. This plugin is based on the powerful tools VisualVM and\nJava Mission Control, both provided by Oracle. This plugin gives you the possibility to use the features provided by\nthese tools also in Minecraft itself. This might be useful for server owners/administrators who cannot use the tools.\n\nFurthermore, it is especially made for Minecraft itself. So you can also check your TPS (Ticks per second), player ping,\nserver timings and so on.\n\n## Features\n\n* Player ping\n* Offline Java version checker\n* Thread safety checks\n* Many details about your setup like Hardware (Disk, Processor, ...) and about your OS \n* Sample CPU usage\n* Analyze RAM usage\n* Access to Stacktraces of running threads\n* Shows your ticks per second with history\n* Shows system performance usage\n* Visual graphs in-game\n* In-game timings viewer\n* Access to Java environment variables (mbeans)\n* Plugin specific profiles\n* Blocking operations on the main thread check\n* Make Heap and Thread dumps\n* Create Java Flight Recorder dump and analyze it later on your own computer\n* Log the server performance into a MySQL/MariaDB database\n\n## Requirements\n\n* Java 8+\n* Spigot 1.8.8+ or a fork of it (ex: Paper)\n\n## Permissions\n\nlagmonitor.* - Access to all LagMonitor features\n\nlagmonitor.commands.* - Access to all commands\n\n### All command permissions\n\n* lagmonitor.command.ping\n* lagmonitor.command.ping.other\n* lagmonitor.command.stacktrace\n* lagmonitor.command.thread\n* lagmonitor.command.tps\n* lagmonitor.command.mbean\n* lagmonitor.command.system\n* lagmonitor.command.environment\n* lagmonitor.command.timing\n* lagmonitor.command.monitor\n* lagmonitor.command.graph\n* lagmonitor.command.native\n* lagmonitor.command.vm\n* lagmonitor.command.network\n* lagmonitor.command.tasks\n* lagmonitor.command.heap\n* lagmonitor.command.jfr\n\n## Commands\n\n    /ping - Gets your server ping\n    /ping <player> - Gets the ping of the selected player\n    /stacktrace - Gets the execution stacktrace of the current thread\n    /stacktrace <threadName> - Gets the execution stacktrace of selected thread\n    /thread - Outputs all running threads with their current state\n    /tpshistory - Outputs the current tps\n    /mbean - List all available mbeans (java environment information, JMX)\n    /mbean <beanName> - List all available attributes of this mbean\n    /mbean <beanName> <attribute> - Outputs the value of this attribute\n    /system - Gives you some general information (Minecraft server related)\n    /env - Gives you some general information (OS related)\n    /timing - Outputs your server timings ingame\n    /monitor [start|stop|paste] - Monitors the CPU usage of methods\n    /graph [heap|cpu|threads|classes] - Gives you visual graph about your server (currently only the heap usage)\n    /native - Gives you some native os information\n    /vm - Outputs vm specific information like garbage collector, class loading or vm specification\n    /network - Shows network interface configuration\n    /tasks - Information about running and pending tasks\n    /heap - Heap dump about your current memory\n    /lagpage <next|prev|pageNumber|save|all> - Pagination command for the current pagination session\n    /jfr <start|stop|dump> - Manages the Java Flight Recordings of the native Java VM. It gives you much more detailed\n        information including network communications, file read/write times, detailed heap and thread data, ...\n\n## Development builds\n\nDevelopment builds of this project can be acquired at the provided CI (continuous integration) server. It contains the\nlatest changes from the Source-Code in preparation for the following release. This means they could contain new\nfeatures, bug fixes and other changes since the last release.\n\nNevertheless builds are only tested using a small set of automated and a few manual tests. Therefore they **could**\ncontain new bugs and are likely to be less stable than released versions.\n\nhttps://ci.codemc.org/job/Games647/job/LagMonitor/changes\n\n## Network requests\n\nThis plugin performs network requests to:\n\n* https://paste.enginehub.org - uploading monitor paste command outputs\n\n## Reproducible builds\n\nThis project supports reproducible builds for enhanced security. In short, this means that the source code matches\nthe generated built jar file. Outputs could vary by operating system (line endings), different JDK\nversions and build timestamp. You can extract this using \n[build-info](https://github.com/apache/maven-studies/tree/maven-buildinfo-plugin). Once you have\nthe configuration to use the same line endings and JDK version, you can use the following command\nto inject a custom build timestamp to complete the configuration.\n\n`mvn clean install -Dproject.build.outputTimestamp=DATE`\n\n## Images\n\n### Heap command\n![heap command](https://i.imgur.com/AzDwYxq.png)\n\n### Timing command\n![timing command](https://i.imgur.com/wAxnIxt.png)\n\n### CPU Graph (blue=process, yellow=system) - Process load\n![cpu graph](https://i.imgur.com/DajnZmP.png)\n\n### Stacktrace and Threads command\n![stacktrace and threads](https://i.imgur.com/XY7r9wz.png)\n\n### Ping Command\n![ping command](https://i.imgur.com/LITJKWw.png)\n\n### Thread Sampler (Monitor command)\n![thread sample](https://i.imgur.com/OXOakN6.png)\n\n### System command\n![system command](https://i.imgur.com/hrIV6bW.png)\n\n### Environment command\n![environment command](https://i.imgur.com/gQwr126.png)\n\n### Heap usage graph (yellow=allocated, blue=used)\n![heap usage map](https://i.imgur.com/Yiz9h6G.png)\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.github.games647</groupId>\n    <!--This have to be in lowercase because it's used by plugin.yml-->\n    <artifactId>lagmonitor</artifactId>\n    <packaging>jar</packaging>\n\n    <name>LagMonitor</name>\n    <version>1.17.3</version>\n\n    <url>https://dev.bukkit.org/bukkit-plugins/LagMonitor/</url>\n    <description>\n        Monitors your Minecraft server for Lags\n    </description>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\n        <maven.compiler.source>10</maven.compiler.source>\n        <maven.compiler.target>10</maven.compiler.target>\n        <maven.compiler.release>10</maven.compiler.release>\n\n        <!-- 5.7.5 uses JNA 5.12.1, so this needs to adjusted in the plugin.yml -->\n        <junit.jupiter.version>5.9.2</junit.jupiter.version>\n        <oshi.version>6.4.1</oshi.version>\n\n        <spigotApi>1.19.4-R0.1-SNAPSHOT</spigotApi>\n    </properties>\n\n    <build>\n        <defaultGoal>install</defaultGoal>\n        <!--Just use the project name to replace an old version of the plugin if the user does only copy-paste-->\n        <finalName>${project.name}</finalName>\n\n        <plugins>\n            <!-- Force an update to this plugin to allow reproducible builds -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <!-- According to the Maven wiki, we have to use at least 3.2 for reproducible builds -->\n                <!-- https://maven.apache.org/guides/mini/guide-reproducible-builds.html -->\n                <version>3.2.0</version>\n            </plugin>\n\n            <!-- Add libraries to the plugin -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>3.2.4</version>\n                <configuration>\n                    <createDependencyReducedPom>false</createDependencyReducedPom>\n                    <relocations>\n                        <relocation>\n                            <pattern>oshi</pattern>\n                            <shadedPattern>lagmonitor.oshi</shadedPattern>\n                            <excludes>\n                                <exclude>*.properties</exclude>\n                                <exclude>oshi.architecture.properties</exclude>\n                                <exclude>oshi.linux.filename.properties</exclude>\n                                <exclude>oshi.macos.versions.properties</exclude>\n                                <exclude>oshi.properties</exclude>\n                                <exclude>oshi.vmmacaddr.properties</exclude>\n                            </excludes>\n                        </relocation>\n                    </relocations>\n                    <artifactSet>\n                        <excludes>\n                            <!-- Exclude native drivers by default for user security reasons -->\n                            <exclude>net.java.dev.jna:jna</exclude>\n                        </excludes>\n                    </artifactSet>\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <!--Expose git variables for version names-->\n            <plugin>\n                <groupId>pl.project13.maven</groupId>\n                <artifactId>git-commit-id-plugin</artifactId>\n                <version>4.9.10</version>\n                <configuration>\n                    <failOnNoGitDirectory>false</failOnNoGitDirectory>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>get-the-git-infos</id>\n                        <goals>\n                            <goal>revision</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <!-- Testing plugin-->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>2.22.2</version>\n            </plugin>\n        </plugins>\n\n        <resources>\n            <resource>\n                <directory>src/main/resources</directory>\n                <!--Replace variables-->\n                <filtering>true</filtering>\n            </resource>\n        </resources>\n    </build>\n\n    <repositories>\n        <!--Bukkit-Server-API -->\n        <repository>\n            <id>spigot-repo</id>\n            <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>\n        </repository>\n\n        <!--Paper repository-->\n        <repository>\n            <id>paper-repo</id>\n            <url>https://repo.papermc.io/repository/maven-snapshots/</url>\n        </repository>\n    </repositories>\n\n    <dependencies>\n        <!--Server API for Paper Timings API-->\n        <dependency>\n            <groupId>io.papermc.paper</groupId>\n            <artifactId>paper-api</artifactId>\n            <version>${spigotApi}</version>\n            <scope>provided</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>net.md-5</groupId>\n                    <artifactId>bungeecord-chat</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <!--Server API for Spigot-->\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>${spigotApi}</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <!-- Native data API -->\n        <dependency>\n            <groupId>com.github.oshi</groupId>\n            <artifactId>oshi-demo</artifactId>\n            <version>${oshi.version}</version>\n            <!-- Excludes the libraries below, because we only need the DetectVm class which has no dependencies on\n            the libraries below-->\n            <exclusions>\n                <exclusion>\n                    <groupId>com.fasterxml.jackson.core</groupId>\n                    <artifactId>jackson-databind</artifactId>\n                </exclusion>\n                <exclusion>\n                    <groupId>org.jfree</groupId>\n                    <artifactId>jfreechart</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <!-- JDK logging bridge to forward logging messages to the plugin logger -->\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-jdk14</artifactId>\n            <version>2.0.7</version>\n        </dependency>\n\n        <!-- Include core explicitly in order to ship the default configuration files in it -->\n        <dependency>\n            <groupId>com.github.oshi</groupId>\n            <artifactId>oshi-core</artifactId>\n            <version>${oshi.version}</version>\n        </dependency>\n\n        <!--MySQL driver that is included in Spigot-->\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n            <version>8.0.32</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <!--Netty work library of Minecraft - This is added to read the amount of bytes which are sent or received-->\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-codec</artifactId>\n            <version>4.1.45.Final</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <!--JUnit 5-->\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter-api</artifactId>\n            <version>${junit.jupiter.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter-engine</artifactId>\n            <version>${junit.jupiter.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <!--Mocking library-->\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-junit-jupiter</artifactId>\n            <version>5.2.0</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.hamcrest</groupId>\n            <artifactId>hamcrest-core</artifactId>\n            <version>2.2</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/LagMonitor.java",
    "content": "package com.github.games647.lagmonitor;\n\nimport com.github.games647.lagmonitor.command.EnvironmentCommand;\nimport com.github.games647.lagmonitor.command.GraphCommand;\nimport com.github.games647.lagmonitor.command.HelpCommand;\nimport com.github.games647.lagmonitor.command.MbeanCommand;\nimport com.github.games647.lagmonitor.command.MonitorCommand;\nimport com.github.games647.lagmonitor.command.NativeCommand;\nimport com.github.games647.lagmonitor.command.NetworkCommand;\nimport com.github.games647.lagmonitor.command.PaginationCommand;\nimport com.github.games647.lagmonitor.command.StackTraceCommand;\nimport com.github.games647.lagmonitor.command.VmCommand;\nimport com.github.games647.lagmonitor.command.dump.FlightCommand;\nimport com.github.games647.lagmonitor.command.dump.HeapCommand;\nimport com.github.games647.lagmonitor.command.dump.ThreadCommand;\nimport com.github.games647.lagmonitor.command.minecraft.PingCommand;\nimport com.github.games647.lagmonitor.command.minecraft.SystemCommand;\nimport com.github.games647.lagmonitor.command.minecraft.TPSCommand;\nimport com.github.games647.lagmonitor.command.minecraft.TasksCommand;\nimport com.github.games647.lagmonitor.command.timing.PaperTimingsCommand;\nimport com.github.games647.lagmonitor.command.timing.SpigotTimingsCommand;\nimport com.github.games647.lagmonitor.listener.BlockingConnectionSelector;\nimport com.github.games647.lagmonitor.listener.GraphListener;\nimport com.github.games647.lagmonitor.listener.PageManager;\nimport com.github.games647.lagmonitor.listener.ThreadSafetyListener;\nimport com.github.games647.lagmonitor.logging.ForwardingLoggerFactory;\nimport com.github.games647.lagmonitor.storage.MonitorSaveTask;\nimport com.github.games647.lagmonitor.storage.NativeSaveTask;\nimport com.github.games647.lagmonitor.storage.Storage;\nimport com.github.games647.lagmonitor.storage.TPSSaveTask;\nimport com.github.games647.lagmonitor.task.IODetectorTask;\nimport com.github.games647.lagmonitor.task.PingManager;\nimport com.github.games647.lagmonitor.task.TPSHistoryTask;\nimport com.github.games647.lagmonitor.threading.BlockingActionManager;\nimport com.github.games647.lagmonitor.threading.BlockingSecurityManager;\nimport com.github.games647.lagmonitor.threading.Injectable;\nimport com.github.games647.lagmonitor.traffic.TrafficReader;\n\nimport java.net.ProxySelector;\nimport java.nio.file.Files;\nimport java.sql.SQLException;\nimport java.time.Duration;\nimport java.util.Optional;\nimport java.util.Timer;\nimport java.util.logging.Level;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.PluginCommand;\nimport org.bukkit.plugin.PluginManager;\nimport org.bukkit.plugin.java.JavaPlugin;\nimport org.bukkit.scheduler.BukkitScheduler;\n\npublic class LagMonitor extends JavaPlugin {\n\n    private static final int DETECTION_THRESHOLD = 10;\n    private static final int HOURS_PER_DAY = 24;\n    private static final int MINUTES_PER_HOUR = 60;\n    private static final int SECONDS_PER_MINUTE = 60;\n\n    private final BlockingActionManager actionManager = new BlockingActionManager(this);\n    private final PageManager pageManager = new PageManager();\n    private final TPSHistoryTask tpsHistoryTask = new TPSHistoryTask();\n    private final NativeManager nativeData = new NativeManager(getLogger(), getDataFolder().toPath());\n\n    private PingManager pingManager;\n    private TrafficReader trafficReader;\n    private Timer blockDetectionTimer;\n    private Timer monitorTimer;\n\n    @Override\n    public void onLoad() {\n        ForwardingLoggerFactory.PARENT_LOGGER = getLogger();\n\n        nativeData.setupNativeAdapter();\n    }\n\n    @Override\n    public void onEnable() {\n        saveDefaultConfig();\n\n        if (Files.notExists(getDataFolder().toPath().resolve(\"default.jfc\"))) {\n            saveResource(\"default.jfc\", false);\n        }\n\n        try {\n            pingManager = new PingManager(this);\n        } catch (ReflectiveOperationException reflectiveEx) {\n            getLogger().log(Level.SEVERE, \"Cannot initialize ping manager\", reflectiveEx);\n        }\n\n        //register schedule tasks\n        BukkitScheduler scheduler = getServer().getScheduler();\n        scheduler.runTaskTimer(this, tpsHistoryTask, 20L, TPSHistoryTask.RUN_INTERVAL);\n        scheduler.runTaskTimer(this, pingManager, 20L, PingManager.PING_INTERVAL);\n\n        //register listeners\n        PluginManager pluginManager = getServer().getPluginManager();\n        pluginManager.registerEvents(new GraphListener(), this);\n        pluginManager.registerEvents(pageManager, this);\n        pluginManager.registerEvents(pingManager, this);\n\n        //add the player to the list in the case the plugin is loaded at runtime\n        Bukkit.getOnlinePlayers().forEach(pingManager::addPlayer);\n\n        if (getConfig().getBoolean(\"traffic-counter\")) {\n            try {\n                trafficReader = new TrafficReader(this);\n            } catch (Exception ex) {\n                getLogger().log(Level.SEVERE, \"Failed to initialize packet reader\", ex);\n            }\n        }\n\n        if (getConfig().getBoolean(\"thread-safety-check\")) {\n            pluginManager.registerEvents(new ThreadSafetyListener(actionManager), this);\n        }\n\n        if (getConfig().getBoolean(\"thread-block-detection\")) {\n            scheduler.runTask(this, () -> {\n                blockDetectionTimer = new Timer(getName() + \"-Thread-Blocking-Detection\");\n                IODetectorTask detectorTask = new IODetectorTask(actionManager, Thread.currentThread());\n                blockDetectionTimer.scheduleAtFixedRate(detectorTask, DETECTION_THRESHOLD, DETECTION_THRESHOLD);\n            });\n        }\n\n        if (getConfig().getBoolean(\"monitor-database\")) {\n            setupMonitoringDatabase();\n        }\n\n        if (getConfig().getBoolean(\"socket-block-detection\")) {\n            scheduler.runTask(this, () -> new BlockingConnectionSelector(actionManager).inject());\n        }\n\n        if (getConfig().getBoolean(\"securityMangerBlockingCheck\")) {\n            if (Runtime.version().feature() < 17) {\n                scheduler.runTask(this, () -> new BlockingSecurityManager(actionManager).inject());\n            }\n        }\n\n        registerCommands();\n    }\n\n    private void setupMonitoringDatabase() {\n        try {\n            String host = getConfig().getString(\"host\");\n            int port = getConfig().getInt(\"port\");\n            String database = getConfig().getString(\"database\");\n            boolean useSSL = getConfig().getBoolean(\"useSSL\");\n\n            String username = getConfig().getString(\"username\");\n            String password = getConfig().getString(\"password\");\n            String tablePrefix = getConfig().getString(\"tablePrefix\");\n            Storage storage = new Storage(getLogger(), host, port, database, useSSL, username, password, tablePrefix);\n            storage.createTables();\n\n            BukkitScheduler scheduler = getServer().getScheduler();\n            scheduler.runTaskTimerAsynchronously(this, new TPSSaveTask(tpsHistoryTask, storage), 20L,\n                     getConfig().getInt(\"tps-save-interval\") * 20L);\n            //this can run async because it runs independently of the main thread\n            scheduler.runTaskTimerAsynchronously(this, new MonitorSaveTask(this, storage),\n                    20L,getConfig().getInt(\"monitor-save-interval\") * 20L);\n            scheduler.runTaskTimerAsynchronously(this, new NativeSaveTask(this, storage),\n                    20L,getConfig().getInt(\"native-save-interval\") * 20L);\n        } catch (SQLException sqlEx) {\n            getLogger().log(Level.SEVERE, \"Failed to setup monitoring database\", sqlEx);\n        }\n    }\n\n    @Override\n    public void onDisable() {\n        if (trafficReader != null) {\n            trafficReader.close();\n            trafficReader = null;\n        }\n\n        close(blockDetectionTimer);\n        blockDetectionTimer = null;\n\n        close(monitorTimer);\n        monitorTimer = null;\n\n        //restore the security manager\n        SecurityManager securityManager = System.getSecurityManager();\n        if (securityManager instanceof BlockingSecurityManager) {\n            ((Injectable) securityManager).restore();\n        }\n\n        ProxySelector proxySelector = ProxySelector.getDefault();\n        if (proxySelector instanceof BlockingConnectionSelector) {\n            ((Injectable) proxySelector).restore();\n        }\n    }\n\n    private void close(Timer timer) {\n        if (timer != null) {\n            timer.cancel();\n            timer.purge();\n        }\n    }\n\n    public PageManager getPageManager() {\n        return pageManager;\n    }\n\n    public Timer getMonitorTimer() {\n        return monitorTimer;\n    }\n\n    public void setMonitorTimer(Timer monitorTimer) {\n        this.monitorTimer = monitorTimer;\n    }\n\n    public TrafficReader getTrafficReader() {\n        return trafficReader;\n    }\n\n    public TPSHistoryTask getTpsHistoryTask() {\n        return tpsHistoryTask;\n    }\n\n    public Optional<PingManager> getPingManager() {\n        return Optional.ofNullable(pingManager);\n    }\n\n    public NativeManager getNativeData() {\n        return nativeData;\n    }\n\n    private void registerCommands() {\n        getCommand(getName()).setExecutor(new HelpCommand(this));\n\n        getCommand(\"ping\").setExecutor(new PingCommand(this));\n        getCommand(\"stacktrace\").setExecutor(new StackTraceCommand(this));\n        getCommand(\"thread\").setExecutor(new ThreadCommand(this));\n        getCommand(\"tpshistory\").setExecutor(new TPSCommand(this));\n        getCommand(\"mbean\").setExecutor(new MbeanCommand(this));\n        getCommand(\"system\").setExecutor(new SystemCommand(this));\n        getCommand(\"env\").setExecutor(new EnvironmentCommand(this));\n        getCommand(\"monitor\").setExecutor(new MonitorCommand(this));\n        getCommand(\"graph\").setExecutor(new GraphCommand(this));\n        getCommand(\"native\").setExecutor(new NativeCommand(this));\n        getCommand(\"vm\").setExecutor(new VmCommand(this));\n        getCommand(\"tasks\").setExecutor(new TasksCommand(this));\n        getCommand(\"heap\").setExecutor(new HeapCommand(this));\n        getCommand(\"lagpage\").setExecutor(new PaginationCommand(this));\n        getCommand(\"jfr\").setExecutor(new FlightCommand(this));\n        getCommand(\"network\").setExecutor(new NetworkCommand(this));\n\n        PluginCommand timing = getCommand(\"timing\");\n        try {\n            //paper moved to class to package co.aikar.timings\n            Class.forName(\"co.aikar.timings.Timing\");\n            timing.setExecutor(new PaperTimingsCommand(this));\n        } catch (ClassNotFoundException e) {\n            timing.setExecutor(new SpigotTimingsCommand(this));\n        }\n    }\n\n    public static String formatDuration(Duration duration) {\n        long seconds = duration.getSeconds();\n        return String.format(\"'%d' days '%d' hours '%d' minutes '%d' seconds'\",\n                duration.toDays(),\n                duration.toHours() % HOURS_PER_DAY,\n                duration.toMinutes() % MINUTES_PER_HOUR,\n                duration.getSeconds() % SECONDS_PER_MINUTE);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/MethodMeasurement.java",
    "content": "package com.github.games647.lagmonitor;\n\nimport com.google.common.collect.ImmutableMap;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.stream.IntStream;\n\npublic class MethodMeasurement implements Comparable<MethodMeasurement> {\n\n    private final String id;\n    private final String className;\n    private final String method;\n\n    private final Map<String, MethodMeasurement> childInvokes = new HashMap<>();\n    private long totalTime;\n\n    public MethodMeasurement(String id, String className, String method) {\n        this.id = id;\n\n        this.className = className;\n        this.method = method;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public String getClassName() {\n        return className;\n    }\n\n    public String getMethod() {\n        return method;\n    }\n\n    public long getTotalTime() {\n        return totalTime;\n    }\n\n    public Map<String, MethodMeasurement> getChildInvokes() {\n        return ImmutableMap.copyOf(childInvokes);\n    }\n\n    public float getTimePercent(long parentTime) {\n        //one float conversion triggers the complete calculation to be decimal\n        return ((float) totalTime / parentTime) * 100;\n    }\n\n    public void onMeasurement(StackTraceElement[] stackTrace, int skipElements, long time) {\n        totalTime += time;\n\n        if (skipElements >= stackTrace.length) {\n            //we reached the end\n            return;\n        }\n\n        StackTraceElement nextChildElement = stackTrace[stackTrace.length - skipElements - 1];\n        String nextClass = nextChildElement.getClassName();\n        String nextMethod = nextChildElement.getMethodName();\n\n        String idName = nextChildElement.getClassName() + '.' + nextChildElement.getMethodName();\n        MethodMeasurement child = childInvokes\n                .computeIfAbsent(idName, (key) -> new MethodMeasurement(key, nextClass, nextMethod));\n        child.onMeasurement(stackTrace, skipElements + 1, time);\n    }\n\n    public void writeString(StringBuilder builder, int indent) {\n        StringBuilder b = new StringBuilder();\n        IntStream.range(0, indent).forEach(i -> b.append(' '));\n\n        String padding = b.toString();\n\n        for (MethodMeasurement child : getChildInvokes().values()) {\n            builder.append(padding).append(child.id).append(\"()\");\n            builder.append(' ');\n            builder.append(child.totalTime).append(\"ms\");\n            builder.append('\\n');\n            child.writeString(builder, indent + 1);\n        }\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        MethodMeasurement that = (MethodMeasurement) o;\n\n        return totalTime == that.totalTime &&\n                Objects.equals(id, that.id) &&\n                Objects.equals(className, that.className) &&\n                Objects.equals(method, that.method) &&\n                Objects.equals(childInvokes, that.childInvokes);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id, className, method, childInvokes, totalTime);\n    }\n\n    @Override\n    public int compareTo(MethodMeasurement other) {\n        return Long.compare(this.totalTime, other.totalTime);\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder builder = new StringBuilder();\n        for (Map.Entry<String, MethodMeasurement> entry : getChildInvokes().entrySet()) {\n            builder.append(entry.getKey()).append(\"()\");\n            builder.append(' ');\n            builder.append(entry.getValue().totalTime).append(\"ms\");\n            builder.append('\\n');\n            entry.getValue().writeString(builder, 1);\n        }\n\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/NativeManager.java",
    "content": "package com.github.games647.lagmonitor;\n\nimport com.sun.management.UnixOperatingSystemMXBean;\n\nimport java.io.IOException;\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.OperatingSystemMXBean;\nimport java.nio.file.FileStore;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Optional;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nimport oshi.SystemInfo;\nimport oshi.hardware.GlobalMemory;\nimport oshi.software.os.OSProcess;\n\npublic class NativeManager {\n\n    private static final String JNA_FILE = \"jna-5.5.0.jar\";\n\n    private final Logger logger;\n    private final Path dataFolder;\n\n    private final OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();\n    private SystemInfo info;\n\n    private int pid = -1;\n\n    public NativeManager(Logger logger, Path dataFolder) {\n        this.logger = logger;\n        this.dataFolder = dataFolder;\n    }\n\n    public void setupNativeAdapter() {\n        logger.info(\"Found JNA native library. Enabling extended native data support to display more data\");\n        try {\n            info = new SystemInfo();\n\n            //make a test call\n            pid = info.getOperatingSystem().getProcessId();\n        } catch (UnsatisfiedLinkError | NoClassDefFoundError linkError) {\n            logger.log(Level.INFO, \"Cannot load native library. Continuing without it...\", linkError);\n            info = null;\n        }\n    }\n\n    public Optional<SystemInfo> getSystemInfo() {\n        return Optional.ofNullable(info);\n    }\n\n    public double getProcessCPULoad() {\n        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {\n            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;\n            return nativeOsBean.getProcessCpuLoad();\n        }\n\n        return -1;\n    }\n\n    public Optional<OSProcess> getProcess() {\n        if (info == null) {\n            return Optional.empty();\n        }\n\n        return Optional.of(info.getOperatingSystem().getProcess(pid));\n    }\n\n    public double getCPULoad() {\n        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {\n            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;\n            return nativeOsBean.getSystemCpuLoad();\n        } else if (info != null) {\n            return info.getHardware().getProcessor().getSystemLoadAverage(1)[0];\n        }\n\n        return -1;\n    }\n\n    public long getOpenFileDescriptors() {\n        if (osBean instanceof com.sun.management.UnixOperatingSystemMXBean) {\n            return ((UnixOperatingSystemMXBean) osBean).getOpenFileDescriptorCount();\n        } else if (info != null) {\n            return info.getOperatingSystem().getFileSystem().getOpenFileDescriptors();\n        }\n\n        return -1;\n    }\n\n    public long getMaxFileDescriptors() {\n        if (osBean instanceof com.sun.management.UnixOperatingSystemMXBean) {\n            return ((UnixOperatingSystemMXBean) osBean).getMaxFileDescriptorCount();\n        } else if (info != null) {\n            return info.getOperatingSystem().getFileSystem().getMaxFileDescriptors();\n        }\n\n        return -1;\n    }\n\n    public long getTotalMemory() {\n        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {\n            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;\n            return nativeOsBean.getTotalPhysicalMemorySize();\n        } else if (info != null) {\n            return info.getHardware().getMemory().getTotal();\n        }\n\n        return -1;\n    }\n\n    public long getFreeMemory() {\n        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {\n            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;\n            return nativeOsBean.getFreePhysicalMemorySize();\n        } else if (info != null) {\n            return getTotalMemory() - info.getHardware().getMemory().getAvailable();\n        }\n\n        return -1;\n    }\n\n    public long getFreeSwap() {\n        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {\n            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;\n            return nativeOsBean.getFreeSwapSpaceSize();\n        } else if (info != null) {\n            GlobalMemory memory = info.getHardware().getMemory();\n            return memory.getAvailable();\n        }\n\n        return -1;\n    }\n\n    public long getTotalSwap() {\n        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {\n            com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean;\n            return nativeOsBean.getTotalSwapSpaceSize();\n        } else if (info != null) {\n            return info.getHardware().getMemory().getVirtualMemory().getSwapTotal();\n        }\n\n        return -1;\n    }\n\n    public long getFreeSpace() {\n        long freeSpace = 0;\n        try {\n            FileStore fileStore = Files.getFileStore(Paths.get(\".\"));\n            freeSpace = fileStore.getUsableSpace();\n        } catch (IOException ioEx) {\n            logger.log(Level.WARNING, \"Cannot calculate free/total disk space\", ioEx);\n        }\n\n        return freeSpace;\n    }\n\n    public long getTotalSpace() {\n        long totalSpace = 0;\n        try {\n            FileStore fileStore = Files.getFileStore(Paths.get(\".\"));\n            totalSpace = fileStore.getTotalSpace();\n        } catch (IOException ioEx) {\n            logger.log(Level.WARNING, \"Cannot calculate free disk space\", ioEx);\n        }\n\n        return totalSpace;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/Pages.java",
    "content": "package com.github.games647.lagmonitor;\n\nimport java.time.LocalTime;\nimport java.time.format.DateTimeFormatter;\nimport java.time.format.FormatStyle;\nimport java.util.List;\n\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ClickEvent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport net.md_5.bungee.api.chat.ComponentBuilder.FormatRetention;\nimport net.md_5.bungee.api.chat.HoverEvent;\n\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\nimport org.bukkit.util.ChatPaginator;\n\npublic class Pages {\n\n    private static final int PAGINATION_LINES = 2;\n\n    private static final int CONSOLE_HEIGHT = 40 - PAGINATION_LINES;\n    private static final int PLAYER_HEIGHT = ChatPaginator.OPEN_CHAT_PAGE_HEIGHT - PAGINATION_LINES;\n\n    public static String filterPackageNames(String packageName) {\n        String text = packageName;\n        if (text.contains(\"net.minecraft.server\")) {\n            text = text.replace(\"net.minecraft.server\", \"NMS\");\n        } else if (text.contains(\"org.bukkit.craftbukkit\")) {\n            text = text.replace(\"org.bukkit.craftbukkit\", \"OBC\");\n        }\n\n        //IDEA: if it's a player we need to shorten the text more aggressively\n        //maybe replacing the package with the plugin name\n        //by getting the package name from the plugin.yml?\n        return text;\n    }\n\n    private final String date = LocalTime.now().format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT));\n    private final String title;\n\n    private final List<BaseComponent[]> lines;\n\n    private int lastSentPage = 1;\n\n    public Pages(String title, List<BaseComponent[]> lines) {\n        this.title = title;\n        this.lines = lines;\n    }\n\n    public int getTotalPages(boolean isPlayer) {\n        if (isPlayer) {\n            return (lines.size() / PLAYER_HEIGHT) + 1;\n        }\n\n        return (lines.size() / CONSOLE_HEIGHT) + 1;\n    }\n\n    public List<BaseComponent[]> getAllLines() {\n        return lines;\n    }\n\n    public int getLastSentPage() {\n        return lastSentPage;\n    }\n\n    public void setLastSentPage(int lastSentPage) {\n        this.lastSentPage = lastSentPage;\n    }\n\n    public List<BaseComponent[]> getPage(int page, boolean isPlayer) {\n        int startIndex;\n        int endIndex;\n        if (isPlayer) {\n            startIndex = (page - 1) * PLAYER_HEIGHT;\n            endIndex = page * PLAYER_HEIGHT;\n        } else {\n            startIndex = (page - 1) * CONSOLE_HEIGHT;\n            endIndex = page * CONSOLE_HEIGHT;\n        }\n\n        if (startIndex >= lines.size()) {\n            endIndex = lines.size() - 1;\n            startIndex = endIndex;\n        } else if (endIndex >= lines.size()) {\n            endIndex = lines.size() - 1;\n        }\n\n        return lines.subList(startIndex, endIndex);\n    }\n\n    public BaseComponent[] buildHeader(int page, int totalPages) {\n        return new ComponentBuilder(title + \" from \" + date)\n                .color(ChatColor.GOLD)\n                .append(\" << \")\n                .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT\n                        , new ComponentBuilder(\"Go to the previous page\").create()))\n                .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, \"/lagpage \" + (page - 1)))\n                .color(ChatColor.DARK_AQUA)\n                .append(page + \" / \" + totalPages, FormatRetention.NONE)\n                .color(ChatColor.GRAY)\n                .append(\" >>\")\n                .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT\n                        , new ComponentBuilder(\"Go to the next page\").create()))\n                .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, \"/lagpage \" + (page + 1)))\n                .color(ChatColor.DARK_AQUA)\n                .create();\n    }\n\n    public String buildFooter(int page, boolean isPlayer) {\n        int endIndex;\n        if (isPlayer) {\n            endIndex = page * PLAYER_HEIGHT;\n        } else {\n            endIndex = page * CONSOLE_HEIGHT;\n        }\n\n        if (endIndex < lines.size()) {\n            //Index starts by 0\n            int remaining = lines.size() - endIndex - 1;\n            return \"... \" + remaining + \" more entries. Click the arrows above or type /lagpage next\";\n        }\n\n        return \"\";\n    }\n\n    public void send(CommandSender sender) {\n        send(sender, 1);\n    }\n\n    public void send(CommandSender sender, int page) {\n        this.lastSentPage = page;\n\n        if (sender instanceof Player) {\n            Player player = (Player) sender;\n            player.spigot().sendMessage(buildHeader(page, getTotalPages(true)));\n            \n            getPage(page, true).forEach(player.spigot()::sendMessage);\n\n            String footer = buildFooter(page, true);\n            if (!footer.isEmpty()) {\n                sender.sendMessage(ChatColor.GOLD + footer);\n            }\n        } else {\n            BaseComponent[] header = buildHeader(page, getTotalPages(false));\n            StringBuilder headerBuilder = new StringBuilder();\n            for (BaseComponent component : header) {\n                headerBuilder.append(component.toLegacyText());\n            }\n\n            sender.sendMessage(headerBuilder.toString());\n            getPage(page, false).stream().map(line -> {\n                StringBuilder lineBuilder = new StringBuilder();\n                for (BaseComponent component : line) {\n                    lineBuilder.append(component.toLegacyText());\n                }\n                \n                return lineBuilder.toString();\n            }).forEach(sender::sendMessage);\n\n            String footer = buildFooter(page, false);\n            if (!footer.isEmpty()) {\n                sender.sendMessage(ChatColor.GOLD + footer);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/EnvironmentCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.NativeManager;\n\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.OperatingSystemMXBean;\nimport java.text.DecimalFormat;\nimport java.util.Map.Entry;\nimport java.util.Optional;\n\nimport oshi.SystemInfo;\nimport oshi.hardware.CentralProcessor;\nimport oshi.hardware.CentralProcessor.ProcessorIdentifier;\nimport oshi.software.os.OperatingSystem;\n\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\n\nimport static com.github.games647.lagmonitor.util.LagUtils.readableBytes;\n\npublic class EnvironmentCommand extends LagCommand {\n\n    public EnvironmentCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();\n\n        //os general info\n        sendMessage(sender, \"OS Name\", osBean.getName());\n        sendMessage(sender, \"OS Arch\", osBean.getArch());\n\n        Optional<SystemInfo> optInfo = plugin.getNativeData().getSystemInfo();\n        if (optInfo.isPresent()) {\n            SystemInfo systemInfo = optInfo.get();\n\n            OperatingSystem osInfo = systemInfo.getOperatingSystem();\n            sendMessage(sender, \"OS family\", osInfo.getFamily());\n            sendMessage(sender, \"OS version\", osInfo.getVersionInfo().toString());\n            sendMessage(sender, \"OS Manufacturer\", osInfo.getManufacturer());\n\n            sendMessage(sender, \"Total processes\", String.valueOf(osInfo.getProcessCount()));\n            sendMessage(sender, \"Total threads\", String.valueOf(osInfo.getThreadCount()));\n        }\n\n        //CPU\n        sender.sendMessage(PRIMARY_COLOR + \"CPU:\");\n        if (optInfo.isPresent()) {\n            CentralProcessor processor = optInfo.get().getHardware().getProcessor();\n            ProcessorIdentifier identifier = processor.getProcessorIdentifier();\n\n            sendMessage(sender, \"    Vendor\", identifier.getVendor());\n            sendMessage(sender, \"    Family\", identifier.getFamily());\n            sendMessage(sender, \"    Name\", identifier.getName());\n            sendMessage(sender, \"    Model\", identifier.getModel());\n            sendMessage(sender, \"    Id\", identifier.getIdentifier());\n            sendMessage(sender, \"    Vendor freq\", String.valueOf(identifier.getVendorFreq()));\n            sendMessage(sender, \"    Physical Cores\", String.valueOf(processor.getPhysicalProcessorCount()));\n        }\n\n        sendMessage(sender, \"    Logical Cores\", String.valueOf(osBean.getAvailableProcessors()));\n        sendMessage(sender, \"    Endian\", System.getProperty(\"sun.cpu.endian\", \"Unknown\"));\n\n        sendMessage(sender, \"Load Average\", String.valueOf(osBean.getSystemLoadAverage()));\n        printExtendOsInfo(sender);\n\n        displayDiskSpace(sender);\n\n        NativeManager nativeData = plugin.getNativeData();\n        sendMessage(sender, \"Open file descriptors\", String.valueOf(nativeData.getOpenFileDescriptors()));\n        sendMessage(sender, \"Max file descriptors\", String.valueOf(nativeData.getMaxFileDescriptors()));\n\n        sender.sendMessage(PRIMARY_COLOR + \"Variables:\");\n        for (Entry<String, String> variable : System.getenv().entrySet()) {\n            sendMessage(sender, \"    \" + variable.getKey(), variable.getValue());\n        }\n\n        return true;\n    }\n\n    private void printExtendOsInfo(CommandSender sender) {\n        NativeManager nativeData = plugin.getNativeData();\n\n        //cpu\n        double systemCpuLoad = nativeData.getCPULoad();\n        double processCpuLoad = nativeData.getProcessCPULoad();\n\n        //these numbers are in percent (0.01 -> 1%)\n        //we want to to have four places in a human readable percent value to multiple it with 100\n        DecimalFormat decimalFormat = new DecimalFormat(\"###.#### %\");\n        decimalFormat.setMultiplier(100);\n        String systemLoadFormat = decimalFormat.format(systemCpuLoad);\n        String processLoadFormat = decimalFormat.format(processCpuLoad);\n\n        sendMessage(sender,\"System Usage\", systemLoadFormat);\n        sendMessage(sender,\"Process Usage\", processLoadFormat);\n\n        //swap\n        long totalSwap = nativeData.getTotalSwap();\n        long freeSwap = nativeData.getFreeSwap();\n        sendMessage(sender, \"Total Swap\", readableBytes(totalSwap));\n        sendMessage(sender, \"Free Swap\", readableBytes(freeSwap));\n\n        //RAM\n        long totalMemory = nativeData.getTotalMemory();\n        long freeMemory = nativeData.getFreeMemory();\n        sendMessage(sender, \"Total OS RAM\", readableBytes(totalMemory));\n        sendMessage(sender, \"Free OS RAM\", readableBytes(freeMemory));\n    }\n\n    private void displayDiskSpace(CommandSender sender) {\n        long freeSpace = plugin.getNativeData().getFreeSpace();\n        long totalSpace = plugin.getNativeData().getTotalSpace();\n\n        //Disk info\n        sendMessage(sender,\"Disk Size\", readableBytes(totalSpace));\n        sendMessage(sender,\"Free Space\", readableBytes(freeSpace));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/GraphCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.graph.ClassesGraph;\nimport com.github.games647.lagmonitor.graph.CombinedGraph;\nimport com.github.games647.lagmonitor.graph.CpuGraph;\nimport com.github.games647.lagmonitor.graph.GraphRenderer;\nimport com.github.games647.lagmonitor.graph.HeapGraph;\nimport com.github.games647.lagmonitor.graph.ThreadsGraph;\nimport com.github.games647.lagmonitor.util.LagUtils;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.ChatColor;\nimport org.bukkit.Material;\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.command.TabExecutor;\nimport org.bukkit.entity.Player;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.inventory.PlayerInventory;\nimport org.bukkit.inventory.meta.ItemMeta;\nimport org.bukkit.inventory.meta.MapMeta;\nimport org.bukkit.map.MapView;\n\nimport static java.util.stream.Collectors.toList;\n\npublic class GraphCommand extends LagCommand implements TabExecutor {\n\n    private static final int MAX_COMBINED = 4;\n\n    private final Map<String, GraphRenderer> graphTypes = new HashMap<>();\n\n    public GraphCommand(LagMonitor plugin) {\n        super(plugin);\n\n        graphTypes.put(\"classes\", new ClassesGraph());\n        graphTypes.put(\"cpu\", new CpuGraph(plugin, plugin.getNativeData()));\n        graphTypes.put(\"heap\", new HeapGraph());\n        graphTypes.put(\"threads\", new ThreadsGraph());\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        if (sender instanceof Player) {\n            Player player = (Player) sender;\n\n            if (args.length > 0) {\n                if (args.length > 1) {\n                    buildCombinedGraph(player, args);\n                } else {\n                    String graph = args[0];\n                    GraphRenderer renderer = graphTypes.get(graph);\n                    if (renderer == null) {\n                        sendError(sender, \"Unknown graph type\");\n                    } else {\n                        giveMap(player, installRenderer(player, renderer));\n                    }\n                }\n\n                return true;\n            }\n\n            //default is heap usage\n            GraphRenderer graphRenderer = graphTypes.get(\"heap\");\n            MapView mapView = installRenderer(player, graphRenderer);\n            giveMap(player, mapView);\n        } else {\n            sendError(sender, \"Not implemented for the console\");\n        }\n\n        return true;\n    }\n\n    @Override\n    public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {\n        if (args.length != 1) {\n            return Collections.emptyList();\n        }\n\n        String lastArg = args[args.length - 1];\n        return graphTypes.keySet().stream()\n                .filter(type -> type.startsWith(lastArg))\n                .sorted(String.CASE_INSENSITIVE_ORDER)\n                .collect(toList());\n    }\n\n    private void buildCombinedGraph(Player player, String[] args) {\n        List<GraphRenderer> renderers = new ArrayList<>();\n        for (String arg : args) {\n            GraphRenderer renderer = graphTypes.get(arg);\n            if (renderer == null) {\n                sendError(player, \"Unknown graph type \" + arg);\n                return;\n            }\n\n            renderers.add(renderer);\n        }\n\n        if (renderers.size() > MAX_COMBINED) {\n            sendError(player, \"Too many graphs\");\n        } else {\n            CombinedGraph combinedGraph = new CombinedGraph(renderers.toArray(new GraphRenderer[0]));\n            MapView view = installRenderer(player, combinedGraph);\n            giveMap(player, view);\n        }\n    }\n\n    private void giveMap(Player player, MapView mapView) {\n        PlayerInventory inventory = player.getInventory();\n\n        ItemStack mapItem;\n        if (LagUtils.isFilledMapSupported()) {\n            mapItem = new ItemStack(Material.FILLED_MAP, 1);\n            ItemMeta meta = mapItem.getItemMeta();\n            if (meta instanceof MapMeta) {\n                MapMeta mapMeta = (MapMeta) meta;\n                mapMeta.setMapView(mapView);\n                mapItem.setItemMeta(meta);\n            }\n        } else {\n            mapItem = new ItemStack(Material.MAP, 1, (short) mapView.getId());\n        }\n\n        inventory.addItem(mapItem);\n        player.sendMessage(ChatColor.DARK_GREEN + \"You received a map with the graph\");\n    }\n\n    private MapView installRenderer(Player player, GraphRenderer graphType) {\n        MapView mapView = Bukkit.createMap(player.getWorld());\n        mapView.getRenderers().forEach(mapView::removeRenderer);\n\n        mapView.addRenderer(graphType);\n        return mapView;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/HelpCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\n\nimport java.util.Map;\nimport java.util.Map.Entry;\n\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport net.md_5.bungee.api.chat.HoverEvent;\nimport net.md_5.bungee.api.chat.HoverEvent.Action;\nimport net.md_5.bungee.api.chat.TextComponent;\n\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\nimport org.bukkit.util.ChatPaginator;\n\npublic class HelpCommand extends LagCommand {\n\n    private static final int HOVER_MAX_LENGTH = 40;\n\n    public HelpCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        sender.sendMessage(ChatColor.AQUA + plugin.getName() + \"-Help\");\n\n        int maxWidth = ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH;\n        if (!(sender instanceof Player)) {\n            maxWidth = Integer.MAX_VALUE;\n        }\n\n        for (Entry<String, Map<String, Object>> entry : plugin.getDescription().getCommands().entrySet()) {\n            String commandKey = entry.getKey();\n            Map<String, Object> value = entry.getValue();\n\n            String description = ' ' + value.getOrDefault(\"description\", \"No description\").toString();\n            String usage = ((String) value.getOrDefault(\"usage\", '/' + commandKey)).replace(\"<command>\", commandKey);\n\n            TextComponent component = createCommandHelp(usage, description, maxWidth);\n            LagCommand.send(sender, component);\n        }\n\n        return true;\n    }\n\n    private TextComponent createCommandHelp(String usage, String description, int maxWidth) {\n        TextComponent usageComponent = new TextComponent(usage);\n        usageComponent.setColor(ChatColor.DARK_AQUA);\n\n        TextComponent descriptionComponent = new TextComponent(description);\n        descriptionComponent.setColor(ChatColor.GOLD);\n        int totalLen = usage.length() + description.length();\n        if (totalLen > maxWidth) {\n            int newDescLength = maxWidth - usage.length() - 3 - 1;\n            if (newDescLength < 0) {\n                newDescLength = 0;\n            }\n\n            String shortDesc = description.substring(0, newDescLength) + \"...\";\n            descriptionComponent.setText(shortDesc);\n\n            ComponentBuilder hoverBuilder = new ComponentBuilder(\"\");\n\n            String[] separated = ChatPaginator.wordWrap(description, HOVER_MAX_LENGTH);\n            for (String line : separated) {\n                hoverBuilder.append(line + '\\n');\n                hoverBuilder.color(ChatColor.GOLD);\n            }\n\n            descriptionComponent.setHoverEvent(new HoverEvent(Action.SHOW_TEXT, hoverBuilder.create()));\n        } else {\n            descriptionComponent.setText(description);\n        }\n\n        usageComponent.addExtra(descriptionComponent);\n        return usageComponent;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/LagCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.TextComponent;\n\nimport org.bukkit.ChatColor;\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandExecutor;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.configuration.file.FileConfiguration;\nimport org.bukkit.entity.Player;\n\npublic abstract class LagCommand implements CommandExecutor {\n\n    protected static final ChatColor PRIMARY_COLOR = ChatColor.DARK_AQUA;\n    protected static final ChatColor SECONDARY_COLOR = ChatColor.GRAY;\n\n    protected static final String NATIVE_NOT_FOUND = \"Native library not found. Please download it and place it \" +\n            \"inside configuration folder of this plugin to see this data\";\n\n    protected final LagMonitor plugin;\n\n    public LagCommand(LagMonitor plugin) {\n        this.plugin = plugin;\n    }\n\n    private boolean isCommandAllowed(Command cmd, CommandSender sender) {\n        if (!(sender instanceof Player)) {\n            return true;\n        }\n\n        FileConfiguration config = plugin.getConfig();\n\n        Collection<String> aliases = new ArrayList<>(cmd.getAliases());\n        aliases.add(cmd.getName());\n        for (String alias : aliases) {\n            List<String> aliasAllowed = config.getStringList(\"allow-\" + alias);\n            if (!aliasAllowed.isEmpty()) {\n                return aliasAllowed.contains(sender.getName());\n            }\n        }\n\n        // allowlist doesn't exist\n        return true;\n    }\n\n    public boolean canExecute(CommandSender sender, Command cmd) {\n        if (!isCommandAllowed(cmd, sender)) {\n            sendError(sender, \"Command not allowed for you!\");\n            return false;\n        }\n\n        return true;\n    }\n\n    protected void sendMessage(CommandSender sender, String title, String value) {\n        sender.sendMessage(PRIMARY_COLOR + title + \": \" + SECONDARY_COLOR + value);\n    }\n\n    protected void sendError(CommandSender sender, String msg) {\n        sender.sendMessage(ChatColor.DARK_RED + msg);\n    }\n\n    public static void send(CommandSender sender, BaseComponent... components) {\n        //CommandSender#sendMessage(BaseComponent[]) was introduced after 1.8. This is a backwards compatible solution\n        if (sender instanceof Player) {\n            sender.spigot().sendMessage(components);\n        } else {\n            sender.sendMessage(TextComponent.toLegacyText(components));\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/MbeanCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\n\nimport java.lang.management.ManagementFactory;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport java.util.stream.Stream;\n\nimport javax.management.MBeanAttributeInfo;\nimport javax.management.MBeanServer;\nimport javax.management.ObjectInstance;\nimport javax.management.ObjectName;\n\nimport org.bukkit.ChatColor;\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.command.TabExecutor;\n\nimport static java.util.stream.Collectors.toList;\n\npublic class MbeanCommand extends LagCommand implements TabExecutor {\n\n    public MbeanCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();\n\n        if (args.length > 0) {\n            try {\n                ObjectName beanObject = ObjectName.getInstance(args[0]);\n                if (args.length > 1) {\n                    Object result = mBeanServer.getAttribute(beanObject, args[1]);\n                    sender.sendMessage(ChatColor.DARK_GREEN + Objects.toString(result));\n                } else {\n                    MBeanAttributeInfo[] attributes = mBeanServer.getMBeanInfo(beanObject).getAttributes();\n                    for (MBeanAttributeInfo attribute : attributes) {\n                        if (\"ObjectName\".equals(attribute.getName())) {\n                            //ignore the object name - it's already known if the user invoke the command\n                            continue;\n                        }\n\n                        sender.sendMessage(ChatColor.YELLOW + attribute.getName());\n                    }\n                }\n            } catch (Exception ex) {\n                plugin.getLogger().log(Level.SEVERE, null, ex);\n            }\n        } else {\n            Set<ObjectInstance> allBeans = mBeanServer.queryMBeans(null, null);\n            allBeans.stream()\n                    .map(ObjectInstance::getObjectName)\n                    .map(ObjectName::getCanonicalName)\n                    .forEach(bean -> sender.sendMessage(ChatColor.DARK_AQUA + bean));\n        }\n\n        return true;\n    }\n\n    @Override\n    public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {\n        String lastArg = args[args.length - 1];\n\n        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();\n\n        Stream<String> result = Stream.empty();\n        if (args.length == 1) {\n            Set<ObjectName> mbeans = mBeanServer.queryNames(null, null);\n            result = mbeans.stream()\n                    .map(ObjectName::getCanonicalName)\n                    .filter(name -> name.startsWith(lastArg));\n        } else if (args.length == 2) {\n            try {\n                ObjectName beanObject = ObjectName.getInstance(args[0]);\n                result = Arrays.stream(mBeanServer.getMBeanInfo(beanObject).getAttributes())\n                        .map(MBeanAttributeInfo::getName)\n                        //ignore the object name - it's already known if the user invoke the command\n                        .filter(attribute -> !\"ObjectName\".equals(attribute));\n            } catch (Exception ex) {\n                plugin.getLogger().log(Level.SEVERE, null, ex);\n            }\n        }\n\n        return result.sorted(String.CASE_INSENSITIVE_ORDER).collect(toList());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/MonitorCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.MethodMeasurement;\nimport com.github.games647.lagmonitor.Pages;\nimport com.github.games647.lagmonitor.task.MonitorTask;\nimport com.google.common.base.Strings;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Timer;\nimport java.util.concurrent.TimeUnit;\n\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ClickEvent;\nimport net.md_5.bungee.api.chat.ClickEvent.Action;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\n\npublic class MonitorCommand extends LagCommand {\n\n    public static final long SAMPLE_INTERVAL = 100L;\n    public static final long SAMPLE_DELAY = TimeUnit.SECONDS.toMillis(1)  / 2;\n\n    private MonitorTask monitorTask;\n\n    public MonitorCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        if (args.length > 0) {\n            String monitorCommand = args[0].toLowerCase();\n            switch (monitorCommand) {\n                case \"start\":\n                    startMonitor(sender);\n                    break;\n                case \"stop\":\n                    stopMonitor(sender);\n                    break;\n                case \"paste\":\n                    pasteMonitor(sender);\n                    break;\n                default:\n                    sendError(sender, \"Invalid command parameter\");\n            }\n        } else if (monitorTask == null) {\n            sendError(sender, \"Monitor is not running\");\n        } else {\n            List<BaseComponent[]> lines = new ArrayList<>();\n            synchronized (this) {\n                MethodMeasurement rootSample = monitorTask.getRootSample();\n                printTrace(lines, 0, rootSample, 0);\n            }\n\n            Pages pagination = new Pages(\"Monitor\", lines);\n            pagination.send(sender);\n            this.plugin.getPageManager().setPagination(sender.getName(), pagination);\n        }\n\n        return true;\n    }\n\n    private void printTrace(List<BaseComponent[]> lines, long parentTime, MethodMeasurement current, int depth) {\n        String space = Strings.repeat(\" \", depth);\n\n        long currentTime = current.getTotalTime();\n        float timePercent = current.getTimePercent(parentTime);\n\n        String clazz = Pages.filterPackageNames(current.getClassName());\n        String method = current.getMethod();\n        lines.add(new ComponentBuilder(space + \"[-] \")\n                .append(clazz + '.')\n                .color(ChatColor.DARK_AQUA)\n                .append(method)\n                .color(ChatColor.DARK_GREEN)\n                .append(' ' + timePercent + \"%\")\n                .color(ChatColor.GRAY)\n                .create());\n\n        Collection<MethodMeasurement> childInvokes = current.getChildInvokes().values();\n        List<MethodMeasurement> sortedList = new ArrayList<>(childInvokes);\n        Collections.sort(sortedList);\n\n        sortedList.forEach((child) -> printTrace(lines, currentTime, child, depth + 1));\n    }\n\n    private void startMonitor(CommandSender sender) {\n        Timer timer = plugin.getMonitorTimer();\n        if (monitorTask == null && timer == null) {\n            timer = new Timer(plugin.getName() + \"-Monitor\");\n            plugin.setMonitorTimer(timer);\n\n            monitorTask = new MonitorTask(plugin.getLogger(), Thread.currentThread().getId());\n            timer.scheduleAtFixedRate(monitorTask, SAMPLE_DELAY, SAMPLE_INTERVAL);\n\n            sender.sendMessage(ChatColor.DARK_GREEN + \"Monitor started\");\n        } else {\n            sendError(sender, \"Monitor task is already running\");\n        }\n    }\n\n    private void stopMonitor(CommandSender sender) {\n        Timer timer = plugin.getMonitorTimer();\n        if (monitorTask == null && timer == null) {\n            sendError(sender, \"Monitor is not running\");\n        } else {\n            monitorTask = null;\n            if (timer != null) {\n                timer.cancel();\n                timer.purge();\n                plugin.setMonitorTimer(null);\n            }\n\n            sender.sendMessage(ChatColor.DARK_GREEN + \"Monitor stopped\");\n        }\n    }\n\n    private void pasteMonitor(final CommandSender sender) {\n        Timer timer = plugin.getMonitorTimer();\n        if (monitorTask == null && timer == null) {\n            sendError(sender, \"Monitor is not running\");\n        }\n\n        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {\n            String reportUrl = monitorTask.paste();\n            if (reportUrl == null) {\n                sendError(sender, \"Error occurred. Please check the console\");\n            } else {\n                String profileUrl = reportUrl + \".profile\";\n                send(sender, new ComponentBuilder(\"Report url: \" + profileUrl)\n                        .color(ChatColor.GREEN)\n                        .event(new ClickEvent(Action.OPEN_URL, profileUrl))\n                        .create());\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/NativeCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.util.LagUtils;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.TimeUnit;\n\nimport oshi.SystemInfo;\nimport oshi.demo.DetectVM;\nimport oshi.hardware.Baseboard;\nimport oshi.hardware.ComputerSystem;\nimport oshi.hardware.Firmware;\nimport oshi.hardware.HWDiskStore;\nimport oshi.hardware.HardwareAbstractionLayer;\nimport oshi.hardware.PhysicalMemory;\nimport oshi.hardware.Sensors;\nimport oshi.software.os.OSFileStore;\nimport oshi.software.os.OperatingSystem;\n\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\n\npublic class NativeCommand extends LagCommand {\n\n    public NativeCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        Optional<SystemInfo> optInfo = plugin.getNativeData().getSystemInfo();\n        if (optInfo.isPresent()) {\n            displayNativeInfo(sender, optInfo.get());\n        } else {\n            sendError(sender, NATIVE_NOT_FOUND);\n        }\n\n        return true;\n    }\n\n    private void displayNativeInfo(CommandSender sender, SystemInfo systemInfo) {\n        HardwareAbstractionLayer hardware = systemInfo.getHardware();\n        OperatingSystem operatingSystem = systemInfo.getOperatingSystem();\n\n        //swap and load is already available in the environment command because MBeans already supports this\n        long uptime = TimeUnit.SECONDS.toMillis(operatingSystem.getSystemUptime());\n        String uptimeFormat = LagMonitor.formatDuration(Duration.ofMillis(uptime));\n        sendMessage(sender, \"OS Uptime\", uptimeFormat);\n\n        String startTime = LagMonitor.formatDuration(Duration.ofMillis(uptime));\n        sendMessage(sender, \"OS Start time\", startTime);\n\n        sendMessage(sender, \"CPU Freq\", Arrays.toString(hardware.getProcessor().getCurrentFreq()));\n        sendMessage(sender, \"CPU Max Freq\", String.valueOf(hardware.getProcessor().getMaxFreq()));\n        sendMessage(sender, \"VM Hypervisor\", DetectVM.identifyVM());\n\n        //disk\n        printDiskInfo(sender, hardware.getDiskStores());\n        displayMounts(sender, operatingSystem.getFileSystem().getFileStores());\n\n        printSensorsInfo(sender, hardware.getSensors());\n        printBoardInfo(sender, hardware.getComputerSystem());\n\n        printRAMInfo(sender, hardware.getMemory().getPhysicalMemory());\n    }\n\n    private void printRAMInfo(CommandSender sender, List<PhysicalMemory> physicalMemories) {\n        sender.sendMessage(PRIMARY_COLOR + \"Memory:\");\n        for (PhysicalMemory memory : physicalMemories) {\n            sendMessage(sender, \"    Label\", memory.getBankLabel());\n            sendMessage(sender, \"    Manufacturer\", memory.getManufacturer());\n            sendMessage(sender, \"    Type\", memory.getMemoryType());\n            sendMessage(sender, \"    Capacity\", LagUtils.readableBytes(memory.getCapacity()));\n            sendMessage(sender, \"    Clock speed\", String.valueOf(memory.getClockSpeed()));\n        }\n    }\n\n    private void printBoardInfo(CommandSender sender, ComputerSystem computerSystem) {\n        sendMessage(sender, \"System Manufacturer\", computerSystem.getManufacturer());\n        sendMessage(sender, \"System model\", computerSystem.getModel());\n        sendMessage(sender, \"Serial number\", computerSystem.getSerialNumber());\n\n        sender.sendMessage(PRIMARY_COLOR + \"Baseboard:\");\n        Baseboard baseboard = computerSystem.getBaseboard();\n        sendMessage(sender, \"    Manufacturer\", baseboard.getManufacturer());\n        sendMessage(sender, \"    Model\", baseboard.getModel());\n        sendMessage(sender, \"    Serial\", baseboard.getVersion());\n        sendMessage(sender, \"    Version\", baseboard.getVersion());\n\n        sender.sendMessage(PRIMARY_COLOR + \"BIOS Firmware:\");\n        Firmware firmware = computerSystem.getFirmware();\n        sendMessage(sender, \"    Manufacturer\", firmware.getManufacturer());\n        sendMessage(sender, \"    Name\", firmware.getName());\n        sendMessage(sender, \"    Description\", firmware.getDescription());\n        sendMessage(sender, \"    Version\", firmware.getVersion());\n\n        sendMessage(sender, \"    Release date\", firmware.getReleaseDate());\n    }\n\n    private void printSensorsInfo(CommandSender sender, Sensors sensors) {\n        double cpuTemperature = sensors.getCpuTemperature();\n        sendMessage(sender, \"CPU Temp °C\", String.valueOf(LagUtils.round(cpuTemperature)));\n        sendMessage(sender, \"Voltage\", String.valueOf(LagUtils.round(sensors.getCpuVoltage())));\n\n        int[] fanSpeeds = sensors.getFanSpeeds();\n        sendMessage(sender, \"Fan speed (rpm)\", Arrays.toString(fanSpeeds));\n    }\n\n    private void printDiskInfo(CommandSender sender, List<HWDiskStore> diskStores) {\n        //disk read write\n        long diskReads = diskStores.stream().mapToLong(HWDiskStore::getReadBytes).sum();\n        long diskWrites = diskStores.stream().mapToLong(HWDiskStore::getWriteBytes).sum();\n\n        sendMessage(sender, \"Disk read bytes\", LagUtils.readableBytes(diskReads));\n        sendMessage(sender, \"Disk write bytes\", LagUtils.readableBytes(diskWrites));\n\n        sender.sendMessage(PRIMARY_COLOR + \"Disks:\");\n        for (HWDiskStore disk : diskStores) {\n            String size = LagUtils.readableBytes(disk.getSize());\n            sendMessage(sender, \"    \" + disk.getName(), disk.getModel() + ' ' + size);\n        }\n    }\n\n    private void displayMounts(CommandSender sender, List<OSFileStore> fileStores) {\n        sender.sendMessage(PRIMARY_COLOR + \"Mounts:\");\n        for (OSFileStore fileStore : fileStores) {\n            printMountInfo(sender, fileStore);\n        }\n    }\n\n    private void printMountInfo(CommandSender sender, OSFileStore fileStore) {\n        String type = fileStore.getType();\n        String desc = fileStore.getDescription();\n\n        long totalSpaceBytes = fileStore.getTotalSpace();\n        String totalSpace = LagUtils.readableBytes(totalSpaceBytes);\n        String usedSpace = LagUtils.readableBytes(totalSpaceBytes - fileStore.getUsableSpace());\n\n        String format = desc + ' ' + type + ' ' + usedSpace + '/' + totalSpace;\n        sendMessage(sender, \"    \" + fileStore.getMount(), format);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/NetworkCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.util.LagUtils;\n\nimport java.util.Arrays;\nimport java.util.Optional;\n\nimport oshi.SystemInfo;\nimport oshi.hardware.NetworkIF;\nimport oshi.software.os.NetworkParams;\n\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\n\npublic class NetworkCommand extends LagCommand {\n\n    public NetworkCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        Optional<SystemInfo> optInfo = plugin.getNativeData().getSystemInfo();\n        if (optInfo.isPresent()) {\n            displayNetworkInfo(sender, optInfo.get());\n        } else {\n            sender.sendMessage(NATIVE_NOT_FOUND);\n        }\n\n        return true;\n    }\n\n    private void displayNetworkInfo(CommandSender sender, SystemInfo systemInfo) {\n        displayGlobalNetworkInfo(sender, systemInfo.getOperatingSystem().getNetworkParams());\n        for (NetworkIF networkInterface : systemInfo.getHardware().getNetworkIFs()) {\n            displayInterfaceInfo(sender, networkInterface);\n        }\n    }\n\n    private void displayGlobalNetworkInfo(CommandSender sender, NetworkParams networkParams) {\n        sendMessage(sender, \"Domain name\", networkParams.getDomainName());\n        sendMessage(sender, \"Host name\", networkParams.getHostName());\n        sendMessage(sender, \"Default IPv4 Gateway\", networkParams.getIpv4DefaultGateway());\n        sendMessage(sender, \"Default IPv6 Gateway\", networkParams.getIpv6DefaultGateway());\n        sendMessage(sender, \"DNS servers\", Arrays.toString(networkParams.getDnsServers()));\n    }\n\n    private void displayInterfaceInfo(CommandSender sender, NetworkIF networkInterface) {\n        sendMessage(sender, \"Name\", networkInterface.getName());\n        sendMessage(sender, \"    Display\", networkInterface.getDisplayName());\n        sendMessage(sender, \"    MAC\", networkInterface.getMacaddr());\n        sendMessage(sender, \"    MTU\", String.valueOf(networkInterface.getMTU()));\n        sendMessage(sender, \"    IPv4\", Arrays.toString(networkInterface.getIPv4addr()));\n        sendMessage(sender, \"    IPv6\", Arrays.toString(networkInterface.getIPv6addr()));\n        sendMessage(sender, \"    Speed\", String.valueOf(networkInterface.getSpeed()));\n\n        String receivedBytes = LagUtils.readableBytes(networkInterface.getBytesRecv());\n        String sentBytes = LagUtils.readableBytes(networkInterface.getBytesSent());\n        sendMessage(sender, \"    Received\", receivedBytes);\n        sendMessage(sender, \"    Sent\", sentBytes);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/PaginationCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.Pages;\nimport com.github.games647.lagmonitor.command.dump.DumpCommand;\nimport com.google.common.primitives.Ints;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.logging.Level;\n\nimport net.md_5.bungee.api.chat.BaseComponent;\n\nimport org.bukkit.ChatColor;\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\npublic class PaginationCommand extends DumpCommand {\n\n    public PaginationCommand(LagMonitor plugin) {\n        super(plugin, \"pagination\", \"txt\");\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        Pages pagination = plugin.getPageManager().getPagination(sender.getName());\n        if (pagination == null) {\n            sendError(sender, \"You have no pagination session\");\n            return true;\n        }\n\n        if (args.length > 0) {\n            String subCommand = args[0].toLowerCase();\n            switch (subCommand) {\n                case \"next\":\n                    onNextPage(pagination, sender);\n                    break;\n                case \"prev\":\n                    onPrevPage(pagination, sender);\n                    break;\n                case \"all\":\n                    onShowAll(pagination, sender);\n                    break;\n                case \"save\":\n                    onSave(pagination, sender);\n                    break;\n                default:\n                    onPageNumber(subCommand, sender, pagination);\n            }\n        } else {\n            sendError(sender, \"Not enough arguments\");\n        }\n\n        return true;\n    }\n\n    private void onPageNumber(String subCommand, CommandSender sender, Pages pagination) {\n        Integer page = Ints.tryParse(subCommand);\n        if (page == null) {\n            sendError(sender, \"Unknown subcommand or not a valid page number\");\n        } else {\n            if (page < 1) {\n                sendError(sender, \"Page number too small\");\n                return;\n            } else if (page > pagination.getTotalPages(sender instanceof Player)) {\n                sendError(sender, \"Page number too high\");\n                return;\n            }\n\n            pagination.send(sender, page);\n        }\n    }\n\n    private void onNextPage(Pages pagination, CommandSender sender) {\n        int lastPage = pagination.getLastSentPage();\n        if (lastPage >= pagination.getTotalPages(sender instanceof Player)) {\n            sendError(sender,\"You are already on the last page\");\n            return;\n        }\n\n        pagination.send(sender, lastPage + 1);\n    }\n\n    private void onPrevPage(Pages pagination, CommandSender sender) {\n        int lastPage = pagination.getLastSentPage();\n        if (lastPage <= 1) {\n            sendError(sender,\"You are already on the first page\");\n            return;\n        }\n\n        pagination.send(sender, lastPage - 1);\n    }\n\n    private void onSave(Pages pagination, CommandSender sender) {\n        StringBuilder lineBuilder = new StringBuilder();\n        for (BaseComponent[] line : pagination.getAllLines()) {\n            for (BaseComponent component : line) {\n                lineBuilder.append(component.toLegacyText());\n            }\n\n            lineBuilder.append('\\n');\n        }\n\n        Path dumpFile = getNewDumpFile();\n        try {\n            Files.write(dumpFile, Collections.singletonList(lineBuilder.toString()));\n            sender.sendMessage(ChatColor.GRAY + \"Dump created: \" + dumpFile.getFileName());\n        } catch (IOException ex) {\n            plugin.getLogger().log(Level.SEVERE, null, ex);\n        }\n    }\n\n    private void onShowAll(Pages pagination, CommandSender sender) {\n        if (sender instanceof Player) {\n            Player player = (Player) sender;\n            player.spigot().sendMessage(pagination.buildHeader(1, 1));\n        } else {\n            BaseComponent[] header = pagination.buildHeader(1, 1);\n            StringBuilder headerBuilder = new StringBuilder();\n            for (BaseComponent component : header) {\n                headerBuilder.append(component.toLegacyText());\n            }\n\n            sender.sendMessage(headerBuilder.toString());\n        }\n\n        pagination.getAllLines().stream().map((line) -> {\n            StringBuilder lineBuilder = new StringBuilder();\n            for (BaseComponent component : line) {\n                lineBuilder.append(component.toLegacyText());\n            }\n\n            return lineBuilder.toString();\n        }).forEach(sender::sendMessage);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/StackTraceCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.Pages;\n\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.ThreadInfo;\nimport java.lang.management.ThreadMXBean;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\n\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.command.TabExecutor;\n\npublic class StackTraceCommand extends LagCommand implements TabExecutor {\n\n    private static final int MAX_DEPTH = 75;\n\n    public StackTraceCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        if (args.length > 0) {\n            String threadName = args[0];\n\n            Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();\n            for (Map.Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet()) {\n                Thread thread = entry.getKey();\n                if (thread.getName().equalsIgnoreCase(threadName)) {\n                    StackTraceElement[] stackTrace = entry.getValue();\n                    printStackTrace(sender, stackTrace);\n                    return true;\n                }\n            }\n\n            sendError(sender, \"No thread with that name found\");\n        } else {\n            ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();\n            ThreadInfo threadInfo = threadBean.getThreadInfo(Thread.currentThread().getId(), MAX_DEPTH);\n            printStackTrace(sender, threadInfo.getStackTrace());\n        }\n\n        return true;\n    }\n\n    private void printStackTrace(CommandSender sender, StackTraceElement[] stackTrace) {\n        List<BaseComponent[]> lines = new ArrayList<>();\n\n        //begin from the top\n        for (int i = stackTrace.length - 1; i >= 0; i--) {\n            lines.add(formatTraceElement(stackTrace[i]));\n        }\n\n        Pages pagination = new Pages(\"Stacktrace\", lines);\n        pagination.send(sender);\n        plugin.getPageManager().setPagination(sender.getName(), pagination);\n    }\n\n    private BaseComponent[] formatTraceElement(StackTraceElement traceElement) {\n        String className = Pages.filterPackageNames(traceElement.getClassName());\n        String methodName = traceElement.getMethodName();\n\n        boolean nativeMethod = traceElement.isNativeMethod();\n        int lineNumber = traceElement.getLineNumber();\n\n        String line = Integer.toString(lineNumber);\n        if (nativeMethod) {\n            line = \"Native\";\n        }\n\n        return new ComponentBuilder(className + '.')\n                .color(PRIMARY_COLOR.asBungee())\n                .append(methodName + ':')\n                .color(ChatColor.DARK_GREEN)\n                .append(line)\n                .color(ChatColor.DARK_PURPLE)\n                .create();\n    }\n\n    @Override\n    public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {\n        List<String> result = new ArrayList<>();\n\n        StringBuilder builder = new StringBuilder();\n        for (String arg : args) {\n            builder.append(arg).append(' ');\n        }\n\n        String requestName = builder.toString();\n\n        ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(false, false);\n        return Arrays.stream(threads)\n                .map(ThreadInfo::getThreadName)\n                .filter(name -> name.startsWith(requestName))\n                .sorted(String.CASE_INSENSITIVE_ORDER)\n                .collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/VmCommand.java",
    "content": "package com.github.games647.lagmonitor.command;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.util.JavaVersion;\n\nimport java.lang.management.ClassLoadingMXBean;\nimport java.lang.management.CompilationMXBean;\nimport java.lang.management.GarbageCollectorMXBean;\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.RuntimeMXBean;\n\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport net.md_5.bungee.api.chat.HoverEvent;\nimport net.md_5.bungee.api.chat.HoverEvent.Action;\n\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\n\npublic class VmCommand extends LagCommand {\n\n    public VmCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        //java version info\n        displayJavaVersion(sender);\n\n        //java paths\n        sendMessage(sender, \"Java lib\", System.getProperty(\"sun.boot.library.path\", \"Unknown\"));\n        sendMessage(sender, \"Java home\", System.getProperty(\"java.home\", \"Unknown\"));\n        sendMessage(sender, \"Temp path\", System.getProperty(\"java.io.tmpdir\", \"Unknown\"));\n\n        displayRuntimeInfo(sender, ManagementFactory.getRuntimeMXBean());\n        displayCompilationInfo(sender, ManagementFactory.getCompilationMXBean());\n        displayClassLoading(sender, ManagementFactory.getClassLoadingMXBean());\n\n        //garbage collector\n        for (GarbageCollectorMXBean collector : ManagementFactory.getGarbageCollectorMXBeans()) {\n            displayCollectorStats(sender, collector);\n        }\n\n        return true;\n    }\n\n    private void displayCompilationInfo(CommandSender sender, CompilationMXBean compilationBean) {\n        sendMessage(sender, \"Compiler name\", compilationBean.getName());\n        sendMessage(sender, \"Compilation time (ms)\", String.valueOf(compilationBean.getTotalCompilationTime()));\n    }\n\n    private void displayRuntimeInfo(CommandSender sender, RuntimeMXBean runtimeBean) {\n        //vm\n        sendMessage(sender, \"Java VM\", runtimeBean.getVmName() + ' ' + runtimeBean.getVmVersion());\n        sendMessage(sender, \"Java vendor\", runtimeBean.getVmVendor());\n\n        //vm specification\n        sendMessage(sender, \"Spec name\", runtimeBean.getSpecName());\n        sendMessage(sender, \"Spec vendor\", runtimeBean.getSpecVendor());\n        sendMessage(sender, \"Spec version\", runtimeBean.getSpecVersion());\n    }\n\n    private void displayCollectorStats(CommandSender sender, GarbageCollectorMXBean collector) {\n        sendMessage(sender, \"Garbage collector\", collector.getName());\n        sendMessage(sender, \"    Count\", String.valueOf(collector.getCollectionCount()));\n        sendMessage(sender, \"    Time (ms)\", String.valueOf(collector.getCollectionTime()));\n    }\n\n    private void displayClassLoading(CommandSender sender, ClassLoadingMXBean classBean) {\n        sendMessage(sender, \"Loaded classes\", String.valueOf(classBean.getLoadedClassCount()));\n        sendMessage(sender, \"Total loaded\", String.valueOf(classBean.getTotalLoadedClassCount()));\n        sendMessage(sender, \"Unloaded classes\", String.valueOf(classBean.getUnloadedClassCount()));\n    }\n\n    private void displayJavaVersion(CommandSender sender) {\n        JavaVersion currentVersion = JavaVersion.detect();\n        LagCommand.send(sender, formatJavaVersion(currentVersion));\n\n        sendMessage(sender, \"Java release date\", System.getProperty(\"java.version.date\", \"n/a\"));\n        sendMessage(sender, \"Class version\", System.getProperty(\"java.class.version\"));\n    }\n\n    private BaseComponent[] formatJavaVersion(JavaVersion version) {\n        ComponentBuilder builder = new ComponentBuilder(\"Java version: \").color(PRIMARY_COLOR.asBungee())\n                .append(version.getRaw()).color(SECONDARY_COLOR.asBungee());\n        if (version.isOutdated()) {\n            builder = builder.append(\" (\").color(ChatColor.WHITE)\n                    .append(\"Outdated\").color(ChatColor.DARK_RED)\n                    .event(new HoverEvent(Action.SHOW_TEXT,\n                            new ComponentBuilder(\"You're running an outdated Java version. \\n\"\n                                    + \"Java 9 and 10 are already released. \\n\"\n                                    + \"Newer versions could improve the performance or include bug or security fixes.\")\n                                    .color(ChatColor.DARK_AQUA).create()))\n                    .append(\")\").color(ChatColor.WHITE);\n        }\n\n        return builder.create();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/dump/DumpCommand.java",
    "content": "package com.github.games647.lagmonitor.command.dump;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.command.LagCommand;\n\nimport java.lang.management.ManagementFactory;\nimport java.nio.file.Path;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\n\nimport javax.management.InstanceNotFoundException;\nimport javax.management.MBeanException;\nimport javax.management.MBeanServer;\nimport javax.management.MalformedObjectNameException;\nimport javax.management.ObjectName;\nimport javax.management.ReflectionException;\n\npublic abstract class DumpCommand extends LagCommand {\n\n    //https://docs.oracle.com/javase/10/docs/jre/api/management/extension/com/sun/management/DiagnosticCommandMBean.html\n    protected static final String DIAGNOSTIC_BEAN = \"com.sun.management:type=DiagnosticCommand\";\n    protected static final String NOT_ORACLE_MSG = \"You are not using Oracle JVM. OpenJDK hasn't implemented it yet\";\n\n    private final String filePrefix;\n    private final String fileExt;\n\n    private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern(\"yyyy-MM-dd-HH-mm-ss\");\n\n    public DumpCommand(LagMonitor plugin, String filePrefix, String fileExt) {\n        super(plugin);\n\n        this.filePrefix = filePrefix;\n        this.fileExt = '.' + fileExt;\n    }\n\n    public Path getNewDumpFile() {\n        String timeSuffix = '-' + LocalDateTime.now().format(dateFormat);\n        Path folder = plugin.getDataFolder().toPath();\n        return folder.resolve(filePrefix + '-' + timeSuffix + fileExt);\n    }\n\n    public Object invokeBeanCommand(String beanName, String command, Object[] args, String[] signature)\n            throws MalformedObjectNameException, MBeanException, InstanceNotFoundException, ReflectionException {\n        MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();\n        ObjectName beanObject = ObjectName.getInstance(beanName);\n\n        return beanServer.invoke(beanObject, command, args, signature);\n    }\n\n    public String invokeDiagnosticCommand(String command, String... args)\n            throws MalformedObjectNameException, ReflectionException, MBeanException, InstanceNotFoundException {\n        return (String) invokeBeanCommand(DIAGNOSTIC_BEAN, command,\n                new Object[]{args}, new String[]{String[].class.getName()});\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/dump/FlightCommand.java",
    "content": "package com.github.games647.lagmonitor.command.dump;\n\nimport com.github.games647.lagmonitor.LagMonitor;\n\nimport java.lang.management.ManagementFactory;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.logging.Level;\n\nimport javax.management.InstanceNotFoundException;\nimport javax.management.JMException;\nimport javax.management.MBeanException;\nimport javax.management.MBeanFeatureInfo;\nimport javax.management.MBeanInfo;\nimport javax.management.MBeanServer;\nimport javax.management.MalformedObjectNameException;\nimport javax.management.ObjectName;\nimport javax.management.ReflectionException;\n\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\n\npublic class FlightCommand extends DumpCommand {\n\n    private static final String START_COMMAND = \"jfrStart\";\n    private static final String STOP_COMMAND = \"jfrStop\";\n    private static final String DUMP_COMMAND = \"jfrDump\";\n\n    private static final String SETTINGS_FILE = \"default.jfc\";\n\n    private final String settingsPath;\n    private final String recordingName;\n\n    private final boolean isSupported;\n\n    public FlightCommand(LagMonitor plugin) {\n        super(plugin, \"flight_recorder\", \"jfr\");\n\n        this.recordingName = plugin.getName() + \"-Record\";\n        this.settingsPath = plugin.getDataFolder().toPath().resolve(SETTINGS_FILE).toAbsolutePath().toString();\n\n        isSupported = areFlightMethodsAvailable();\n    }\n\n    private boolean areFlightMethodsAvailable() {\n        MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();\n        try {\n            ObjectName objectName = ObjectName.getInstance(DIAGNOSTIC_BEAN);\n            MBeanInfo beanInfo = beanServer.getMBeanInfo(objectName);\n            return Arrays.stream(beanInfo.getOperations())\n                    .map(MBeanFeatureInfo::getName)\n                    .anyMatch(op -> op.contains(\"jfr\"));\n        } catch (JMException instanceNotFoundEx) {\n            return false;\n        }\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        if (!isSupported) {\n            sendError(sender, NOT_ORACLE_MSG);\n            return true;\n        }\n\n        try {\n            if (args.length > 0) {\n                String subCommand = args[0].toLowerCase();\n                switch (subCommand) {\n                    case \"start\":\n                        onStartCommand(sender);\n                        break;\n                    case \"stop\":\n                        onStopCommand(sender);\n                        break;\n                    case \"dump\":\n                        onDumpCommand(sender);\n                        break;\n                    default:\n                        sendError(sender, \"Unknown subcommand\");\n                }\n            } else {\n                sendError(sender, \"Not enough arguments\");\n            }\n        } catch (InstanceNotFoundException notFoundEx) {\n            sendError(sender, NOT_ORACLE_MSG);\n        } catch (Exception ex) {\n            plugin.getLogger().log(Level.SEVERE, null, ex);\n            sendError(sender, \"An exception occurred. Please check the server log\");\n        }\n\n        return true;\n    }\n\n    private void onStartCommand(CommandSender sender)\n            throws MalformedObjectNameException, ReflectionException, MBeanException, InstanceNotFoundException {\n        String reply = invokeDiagnosticCommand(START_COMMAND, \"settings=\" + settingsPath, \"name=\" + recordingName);\n        sender.sendMessage(reply);\n    }\n\n    private void onStopCommand(CommandSender sender)\n            throws MalformedObjectNameException, ReflectionException, MBeanException, InstanceNotFoundException {\n        String reply = invokeDiagnosticCommand(STOP_COMMAND, \"name=\" + recordingName);\n        sender.sendMessage(reply);\n    }\n\n    private void onDumpCommand(CommandSender sender)\n            throws MalformedObjectNameException, ReflectionException, MBeanException, InstanceNotFoundException {\n        Path dumpFile = getNewDumpFile();\n        String reply = invokeDiagnosticCommand(DUMP_COMMAND\n                , \"filename=\" + dumpFile.toAbsolutePath(), \"name=\" + recordingName);\n\n        sender.sendMessage(reply);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/dump/HeapCommand.java",
    "content": "package com.github.games647.lagmonitor.command.dump;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.Pages;\nimport com.sun.management.HotSpotDiagnosticMXBean;\n\nimport java.lang.management.ManagementFactory;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.logging.Level;\n\nimport javax.management.InstanceNotFoundException;\n\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\n\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\n\npublic class HeapCommand extends DumpCommand {\n\n    private static final String HEAP_COMMAND = \"gcClassHistogram\";\n    private static final boolean DUMP_DEAD_OBJECTS = false;\n\n    private static final String[] EMPTY_STRING = {};\n\n    public HeapCommand(LagMonitor plugin) {\n        super(plugin, \"heap\", \"hprof\");\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        if (args.length > 0) {\n            String subCommand = args[0];\n            if (\"dump\".equalsIgnoreCase(subCommand)) {\n                onDump(sender);\n            } else {\n                sendError(sender, \"Unknown subcommand\");\n            }\n\n            return true;\n        }\n\n        List<BaseComponent[]> paginatedLines = new ArrayList<>();\n        try {\n            String reply = invokeDiagnosticCommand(HEAP_COMMAND, EMPTY_STRING);\n            for (String line : reply.split(\"\\n\")) {\n                paginatedLines.add(new ComponentBuilder(line).create());\n            }\n\n            Pages pagination = new Pages(\"Heap\", paginatedLines);\n            pagination.send(sender);\n            plugin.getPageManager().setPagination(sender.getName(), pagination);\n        } catch (InstanceNotFoundException instanceNotFoundException) {\n            sendError(sender, NOT_ORACLE_MSG);\n        } catch (Exception ex) {\n            plugin.getLogger().log(Level.SEVERE, null, ex);\n            sendError(sender, \"An exception occurred. Please check the server log\");\n        }\n\n        return true;\n    }\n\n    private void onDump(CommandSender sender) {\n        try {\n            //test if this class is available\n            Class.forName(\"com.sun.management.HotSpotDiagnosticMXBean\");\n\n            //can be useful for dumping heaps in binary format\n            HotSpotDiagnosticMXBean hostSpot = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);\n\n            Path dumpFile = getNewDumpFile();\n            hostSpot.dumpHeap(dumpFile.toAbsolutePath().toString(), DUMP_DEAD_OBJECTS);\n\n            sender.sendMessage(ChatColor.GRAY + \"Dump created: \" + dumpFile.getFileName());\n            sender.sendMessage(ChatColor.GRAY + \"You can analyse it using VisualVM\");\n        } catch (ClassNotFoundException notFoundEx) {\n            sendError(sender, NOT_ORACLE_MSG);\n        } catch (Exception ex) {\n            plugin.getLogger().log(Level.SEVERE, null, ex);\n            sendError(sender, \"An exception occurred. Please check the server log\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/dump/ThreadCommand.java",
    "content": "package com.github.games647.lagmonitor.command.dump;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.Pages;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.logging.Level;\n\nimport javax.management.InstanceNotFoundException;\n\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ClickEvent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport net.md_5.bungee.api.chat.HoverEvent;\n\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\n\npublic class ThreadCommand extends DumpCommand {\n\n    private static final String DUMP_COMMAND = \"threadPrint\";\n    private static final String[] EMPTY_STRING_ARRAY = {};\n\n    public ThreadCommand(LagMonitor plugin) {\n        super(plugin, \"thread\", \"tdump\");\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        if (args.length > 0) {\n            String subCommand = args[0];\n            if (\"dump\".equalsIgnoreCase(subCommand)) {\n                onDump(sender);\n            } else {\n                sender.sendMessage(label);\n            }\n\n            return true;\n        }\n\n        List<BaseComponent[]> lines = new ArrayList<>();\n\n        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();\n        for (Thread thread : allStackTraces.keySet()) {\n            if (thread.getContextClassLoader() == null) {\n                //ignore java system threads like reference handler\n                continue;\n            }\n\n            BaseComponent[] components = new ComponentBuilder(\"ID-\" + thread.getId() + \": \")\n                    .color(PRIMARY_COLOR.asBungee())\n                    .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND\n                            , \"/stacktrace \" + thread.getName()))\n                    .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT\n                            , new ComponentBuilder(\"Show the stacktrace\").create()))\n                    .append(thread.getName() + ' ')\n                    .color(ChatColor.GOLD)\n                    .append(thread.getState().toString())\n                    .color(SECONDARY_COLOR.asBungee())\n                    .create();\n            lines.add(components);\n        }\n\n        Pages pagination = new Pages(\"Threads\", lines);\n        pagination.send(sender);\n        plugin.getPageManager().setPagination(sender.getName(), pagination);\n        return true;\n    }\n\n    private void onDump(CommandSender sender) {\n        try {\n            String result = invokeDiagnosticCommand(DUMP_COMMAND, EMPTY_STRING_ARRAY);\n\n            Path dumpFile = getNewDumpFile();\n            Files.write(dumpFile, Collections.singletonList(result));\n\n            sender.sendMessage(ChatColor.GRAY + \"Dump created: \" + dumpFile.getFileName());\n            sender.sendMessage(ChatColor.GRAY + \"You can analyse it using VisualVM\");\n        } catch (InstanceNotFoundException instanceNotFoundException) {\n            sendError(sender, NOT_ORACLE_MSG);\n        } catch (Exception ex) {\n            plugin.getLogger().log(Level.SEVERE, null, ex);\n            sendError(sender, \"An exception occurred. Please check the server log\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/minecraft/PingCommand.java",
    "content": "package com.github.games647.lagmonitor.command.minecraft;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.command.LagCommand;\nimport com.github.games647.lagmonitor.util.LagUtils;\nimport com.github.games647.lagmonitor.util.RollingOverHistory;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.ChatColor;\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\npublic class PingCommand extends LagCommand {\n\n    public PingCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        if (args.length > 0) {\n            displayPingOther(sender, command, args[0]);\n        } else if (sender instanceof Player) {\n            displayPingSelf(sender);\n        } else {\n            sendError(sender, \"You have to be in game in order to see your own ping\");\n        }\n\n        return true;\n    }\n\n    private void displayPingSelf(CommandSender sender) {\n        RollingOverHistory history = plugin.getPingManager().map(m -> m.getHistory(sender.getName())).orElse(null);\n        if (history == null) {\n            sendError(sender, \"Sorry there is currently no data available\");\n            return;\n        }\n\n        int lastPing = (int) history.getLastSample();\n        sender.sendMessage(PRIMARY_COLOR + \"Your ping is: \" + ChatColor.DARK_GREEN + lastPing + \"ms\");\n\n        float pingAverage = (float) (Math.round(history.getAverage() * 100.0) / 100.0);\n        sender.sendMessage(PRIMARY_COLOR + \"Average: \" + ChatColor.DARK_GREEN + pingAverage + \"ms\");\n    }\n\n    private void displayPingOther(CommandSender sender, Command command, String playerName) {\n        if (sender.hasPermission(command.getPermission() + \".other\")) {\n            RollingOverHistory history = plugin.getPingManager().map(m -> m.getHistory(sender.getName())).orElse(null);\n            if (history == null || !canSee(sender, playerName)) {\n                sendError(sender, \"No data for that player \" + playerName);\n                return;\n            }\n\n            int lastPing = (int) history.getLastSample();\n\n            sender.sendMessage(ChatColor.WHITE + playerName + PRIMARY_COLOR + \"'s ping is: \"\n                    + ChatColor.DARK_GREEN + lastPing + \"ms\");\n\n            float pingAverage = LagUtils.round(history.getAverage());\n            sender.sendMessage(PRIMARY_COLOR + \"Average: \" + ChatColor.DARK_GREEN + pingAverage + \"ms\");\n        } else {\n            sendError(sender, \"You don't have enough permission\");\n        }\n    }\n\n    private boolean canSee(CommandSender sender, String playerName) {\n        if (sender instanceof Player) {\n            return ((Player) sender).canSee(Bukkit.getPlayerExact(playerName));\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/minecraft/SystemCommand.java",
    "content": "package com.github.games647.lagmonitor.command.minecraft;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.command.LagCommand;\nimport com.github.games647.lagmonitor.traffic.TrafficReader;\nimport com.github.games647.lagmonitor.util.LagUtils;\nimport com.google.common.base.StandardSystemProperty;\n\nimport java.io.File;\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.RuntimeMXBean;\nimport java.lang.management.ThreadMXBean;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Stream;\n\nimport oshi.software.os.OSProcess;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.Chunk;\nimport org.bukkit.World;\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.plugin.Plugin;\n\nimport static com.github.games647.lagmonitor.util.LagUtils.readableBytes;\n\npublic class SystemCommand extends LagCommand {\n\n    public SystemCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        displayRuntimeInfo(sender, ManagementFactory.getRuntimeMXBean());\n        displayThreadInfo(sender, ManagementFactory.getThreadMXBean());\n        displayProcessInfo(sender);\n        displayUserInfo(sender);\n        displayMinecraftInfo(sender);\n        return true;\n    }\n\n    private void displayUserInfo(CommandSender sender) {\n        sender.sendMessage(PRIMARY_COLOR + \"User\");\n\n        sendMessage(sender, \"    Timezone\", System.getProperty(\"user.timezone\", \"Unknown\"));\n        sendMessage(sender, \"    Country\", System.getProperty(\"user.country\", \"Unknown\"));\n        sendMessage(sender, \"    Language\", System.getProperty(\"user.language\", \"Unknown\"));\n        sendMessage(sender, \"    Home\", StandardSystemProperty.USER_HOME.value());\n        sendMessage(sender, \"    Name\", StandardSystemProperty.USER_NAME.value());\n    }\n\n    private void displayProcessInfo(CommandSender sender) {\n        sender.sendMessage(PRIMARY_COLOR + \"Process:\");\n\n        Optional<OSProcess> optProcess = plugin.getNativeData().getProcess();\n        if (optProcess.isPresent()) {\n            OSProcess process = optProcess.get();\n\n            sendMessage(sender, \"    PID\", String.valueOf(process.getProcessID()));\n            sendMessage(sender, \"    Name\", process.getName());\n            sendMessage(sender, \"    Path\", process.getPath());\n            sendMessage(sender, \"    Working directory\", process.getCurrentWorkingDirectory());\n            sendMessage(sender, \"    User\", process.getUser());\n            sendMessage(sender, \"    Group\", process.getGroup());\n        } else {\n            sendError(sender, NATIVE_NOT_FOUND);\n        }\n    }\n\n    private void displayRuntimeInfo(CommandSender sender, RuntimeMXBean runtimeBean) {\n        long uptime = runtimeBean.getUptime();\n        String uptimeFormat = LagMonitor.formatDuration(Duration.ofMillis(uptime));\n\n        displayMemoryInfo(sender, Runtime.getRuntime());\n\n        // runtime specific\n        sendMessage(sender, \"Uptime\", uptimeFormat);\n        sendMessage(sender, \"Arguments\", runtimeBean.getInputArguments().toString());\n        sendMessage(sender, \"Classpath\", runtimeBean.getClassPath());\n        sendMessage(sender, \"Library path\", runtimeBean.getLibraryPath());\n    }\n\n    private void displayThreadInfo(CommandSender sender, ThreadMXBean threadBean) {\n        sendMessage(sender, \"Threads\", String.valueOf(threadBean.getThreadCount()));\n        sendMessage(sender, \"Peak threads\", String.valueOf(threadBean.getPeakThreadCount()));\n        sendMessage(sender, \"Daemon threads\", String.valueOf(threadBean.getDaemonThreadCount()));\n        sendMessage(sender, \"Total started threads\", String.valueOf(threadBean.getTotalStartedThreadCount()));\n    }\n\n    private void displayMemoryInfo(CommandSender sender, Runtime runtime) {\n        long maxMemory = runtime.maxMemory();\n        long freeMemory = runtime.freeMemory();\n        long totalMemory = runtime.totalMemory();\n\n        sendMessage(sender, \"Reserved used RAM\", readableBytes(totalMemory - freeMemory));\n        sendMessage(sender, \"Reserved free RAM\", readableBytes(freeMemory));\n        sendMessage(sender, \"Reserved RAM\", readableBytes(totalMemory));\n        sendMessage(sender, \"Max RAM\", readableBytes(maxMemory));\n    }\n\n    private void displayMinecraftInfo(CommandSender sender) {\n        //Minecraft specific\n        sendMessage(sender, \"TPS\", String.valueOf(plugin.getTpsHistoryTask().getLastSample()));\n\n        TrafficReader trafficReader = plugin.getTrafficReader();\n        if (trafficReader != null) {\n            String formattedIncoming = readableBytes(trafficReader.getIncomingBytes().longValue());\n            String formattedOutgoing = readableBytes(trafficReader.getOutgoingBytes().longValue());\n            sendMessage(sender, \"Incoming Traffic\", formattedIncoming);\n            sendMessage(sender, \"Outgoing Traffic\", formattedOutgoing);\n        }\n\n        Plugin[] plugins = Bukkit.getPluginManager().getPlugins();\n        sendMessage(sender, \"Loaded Plugins\", String.format(\"%d/%d\", getEnabledPlugins(plugins), plugins.length));\n\n        int onlinePlayers = Bukkit.getOnlinePlayers().size();\n        int maxPlayers = Bukkit.getMaxPlayers();\n        sendMessage(sender, \"Players\", String.format(\"%d/%d\", onlinePlayers, maxPlayers));\n\n        displayWorldInfo(sender);\n        sendMessage(sender, \"Server version\", Bukkit.getVersion());\n    }\n\n    private void displayWorldInfo(CommandSender sender) {\n        int entities = 0;\n        int chunks = 0;\n        int livingEntities = 0;\n        int tileEntities = 0;\n\n        long usedWorldSize = 0;\n\n        List<World> worlds = Bukkit.getWorlds();\n        for (World world : worlds) {\n            for (Chunk loadedChunk : world.getLoadedChunks()) {\n                tileEntities += loadedChunk.getTileEntities().length;\n            }\n\n            livingEntities += world.getLivingEntities().size();\n            entities += world.getEntities().size();\n            chunks += world.getLoadedChunks().length;\n\n            File worldFolder = Bukkit.getWorld(world.getUID()).getWorldFolder();\n            usedWorldSize += LagUtils.getFolderSize(plugin.getLogger(), worldFolder.toPath());\n        }\n\n        sendMessage(sender, \"Entities\", String.format(\"%d/%d\", livingEntities, entities));\n        sendMessage(sender, \"Tile Entities\", String.valueOf(tileEntities));\n        sendMessage(sender, \"Loaded Chunks\", String.valueOf(chunks));\n        sendMessage(sender, \"Worlds\", String.valueOf(worlds.size()));\n        sendMessage(sender, \"World Size\", readableBytes(usedWorldSize));\n    }\n\n    private int getEnabledPlugins(Plugin[] plugins) {\n        return (int) Stream.of(plugins).filter(Plugin::isEnabled).count();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/minecraft/TPSCommand.java",
    "content": "package com.github.games647.lagmonitor.command.minecraft;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.command.LagCommand;\nimport com.github.games647.lagmonitor.task.TPSHistoryTask;\nimport com.google.common.collect.Lists;\n\nimport java.text.DecimalFormat;\nimport java.util.List;\nimport java.util.stream.IntStream;\n\nimport org.bukkit.ChatColor;\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\nimport org.bukkit.util.ChatPaginator;\n\npublic class TPSCommand extends LagCommand {\n\n    private static final ChatColor PRIMARY_COLOR = ChatColor.DARK_AQUA;\n    private static final ChatColor SECONDARY_COLOR = ChatColor.GRAY;\n\n    private static final char EMPTY_CHAR = ' ';\n    private static final char GRAPH_CHAR = '+';\n\n    private static final char PLAYER_EMPTY_CHAR = '▂';\n    private static final char PLAYER_GRAPH_CHAR = '▇';\n\n    private static final int GRAPH_WIDTH = 60 / 2;\n    private static final int GRAPH_LINES = ChatPaginator.CLOSED_CHAT_PAGE_HEIGHT - 3;\n\n    public TPSCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        List<StringBuilder> graphLines = Lists.newArrayListWithExpectedSize(GRAPH_LINES);\n        IntStream.rangeClosed(1, GRAPH_LINES)\n                .map(i -> GRAPH_WIDTH * 2)\n                .mapToObj(StringBuilder::new)\n                .forEach(graphLines::add);\n\n        TPSHistoryTask tpsHistoryTask = plugin.getTpsHistoryTask();\n\n        boolean console = true;\n        if (sender instanceof Player) {\n            console = false;\n        }\n\n        float[] lastSeconds = tpsHistoryTask.getMinuteSample().getSamples();\n        int position = tpsHistoryTask.getMinuteSample().getCurrentPosition();\n        buildGraph(lastSeconds, position, graphLines, console);\n        graphLines.stream().map(Object::toString).forEach(sender::sendMessage);\n\n        printAverageHistory(tpsHistoryTask, sender);\n        sender.sendMessage(PRIMARY_COLOR + \"Current TPS: \" + tpsHistoryTask.getLastSample());\n        return true;\n    }\n\n    private void printAverageHistory(TPSHistoryTask tpsHistoryTask, CommandSender sender) {\n        float minuteAverage = tpsHistoryTask.getMinuteSample().getAverage();\n        float quarterAverage = tpsHistoryTask.getQuarterSample().getAverage();\n        float halfHourAverage = tpsHistoryTask.getHalfHourSample().getAverage();\n\n        DecimalFormat formatter = new DecimalFormat(\"###.##\");\n        sender.sendMessage(PRIMARY_COLOR + \"Last Samples (1m, 15m, 30m): \" + SECONDARY_COLOR\n                + formatter.format(minuteAverage)\n                + ' ' + formatter.format(quarterAverage)\n                + ' ' + formatter.format(halfHourAverage));\n    }\n\n    private void buildGraph(float[] lastSeconds, int lastPos, List<StringBuilder> graphLines, boolean console) {\n        int index = lastPos;\n        //in x-direction\n        for (int xPos = 1; xPos < GRAPH_WIDTH; xPos++) {\n            index++;\n            if (index == lastSeconds.length) {\n                index = 0;\n            }\n\n            float sampleSecond = lastSeconds[index];\n            buildLine(sampleSecond, graphLines, console);\n        }\n    }\n\n    private void buildLine(float sampleSecond, List<StringBuilder> graphLines, boolean console) {\n        ChatColor color = ChatColor.DARK_RED;\n        int lines = 0;\n        if (sampleSecond > 19.5F) {\n            lines = GRAPH_LINES;\n            color = ChatColor.DARK_GREEN;\n        } else if (sampleSecond > 18.0F) {\n            lines = GRAPH_LINES - 1;\n            color = ChatColor.GREEN;\n        } else if (sampleSecond > 17.0F) {\n            lines = GRAPH_LINES - 2;\n            color = ChatColor.YELLOW;\n        } else if (sampleSecond > 15.0F) {\n            lines = GRAPH_LINES - 3;\n            color = ChatColor.GOLD;\n        } else if (sampleSecond > 12.0F) {\n            lines = GRAPH_LINES - 4;\n            color = ChatColor.RED;\n        }\n\n        //in y-direction in reverse order\n        for (int line = GRAPH_LINES - 1; line >= 0; line--) {\n            if (lines == 0) {\n                graphLines.get(line).append(ChatColor.WHITE).append(console ? EMPTY_CHAR : PLAYER_EMPTY_CHAR);\n                continue;\n            }\n\n            lines--;\n            graphLines.get(line).append(color).append(console ? GRAPH_CHAR : PLAYER_GRAPH_CHAR);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/minecraft/TasksCommand.java",
    "content": "package com.github.games647.lagmonitor.command.minecraft;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.Pages;\nimport com.github.games647.lagmonitor.command.LagCommand;\nimport com.github.games647.lagmonitor.traffic.Reflection;\n\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.plugin.Plugin;\nimport org.bukkit.scheduler.BukkitTask;\n\npublic class TasksCommand extends LagCommand {\n\n    private static final MethodHandle taskHandle;\n\n    static {\n        Class<?> taskClass = Reflection.getCraftBukkitClass(\"scheduler.CraftTask\");\n\n        MethodHandle localHandle = null;\n        try {\n            localHandle = MethodHandles.publicLookup().findGetter(taskClass, \"task\", Runnable.class);\n        } catch (NoSuchFieldException | IllegalAccessException noSuchFieldEx) {\n            //ignore\n        }\n\n        taskHandle = localHandle;\n    }\n\n    public TasksCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        List<BaseComponent[]> lines = new ArrayList<>();\n\n        List<BukkitTask> pendingTasks = Bukkit.getScheduler().getPendingTasks();\n        for (BukkitTask pendingTask : pendingTasks) {\n            lines.add(formatTask(pendingTask));\n\n            Class<?> runnableClass = getRunnableClass(pendingTask);\n            if (runnableClass != null) {\n                lines.add(new ComponentBuilder(\"    Task: \")\n                        .color(PRIMARY_COLOR.asBungee())\n                        .append(runnableClass.getSimpleName())\n                        .color(SECONDARY_COLOR.asBungee())\n                        .create());\n            }\n        }\n\n        Pages pagination = new Pages(\"Stacktrace\", lines);\n        pagination.send(sender);\n        plugin.getPageManager().setPagination(sender.getName(), pagination);\n        return true;\n    }\n\n    private BaseComponent[] formatTask(BukkitTask pendingTask) {\n        Plugin owner = pendingTask.getOwner();\n        int taskId = pendingTask.getTaskId();\n        boolean sync = pendingTask.isSync();\n\n        String id = Integer.toString(taskId);\n        if (sync) {\n            id += \"-Sync\";\n        } else if (Bukkit.getScheduler().isCurrentlyRunning(taskId)) {\n            id += \"-Running\";\n        }\n\n        return new ComponentBuilder(owner.getName())\n                .color(PRIMARY_COLOR.asBungee())\n                .append('-' + id)\n                .color(SECONDARY_COLOR.asBungee())\n                .create();\n    }\n\n    private Class<?> getRunnableClass(BukkitTask task) {\n        try {\n            return taskHandle.invokeExact(task).getClass();\n        } catch (Exception ex) {\n            //ignore\n        } catch (Throwable throwable) {\n            throw (Error) throwable;\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/timing/PaperTimingsCommand.java",
    "content": "package com.github.games647.lagmonitor.command.timing;\n\nimport co.aikar.timings.TimingHistory;\nimport co.aikar.timings.Timings;\nimport co.aikar.timings.TimingsManager;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.Pages;\nimport com.github.games647.lagmonitor.traffic.Reflection;\nimport com.google.common.collect.EvictingQueue;\nimport com.google.common.collect.Maps;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport net.md_5.bungee.api.chat.TextComponent;\n\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.configuration.file.YamlConfiguration;\n\nimport static com.github.games647.lagmonitor.util.LagUtils.round;\n\n/**\n * Paper and Sponge uses a new timings system (v2).\n * Missing data:\n * * TicksRecord\n * -> player ticks\n * -> timedTicks\n * -> entityTicks\n * -> activatedEntityTicks\n * -> tileEntityTicks\n * * MinuteReport\n * -> time\n * -> tps\n * -> avgPing\n * -> fullServerTick\n * -> ticks\n * * World data\n * -> worldName\n * -> tileEntities\n * -> entities\n *\n * => This concludes to the fact that the big benefits from Timings v2 isn't available. For example you cannot\n * scroll through your history\n */\npublic class PaperTimingsCommand extends TimingCommand {\n\n    //TODO: Change to MethodHandles\n    private static final String TIMINGS_PACKAGE = \"co.aikar.timings\";\n\n    private static final String EXPORT_CLASS = TIMINGS_PACKAGE + '.' + \"TimingsExport\";\n    private static final String HANDLER_CLASS = TIMINGS_PACKAGE + '.' + \"TimingHandler\";\n    private static final String HISTORY_ENTRY_CLASS = TIMINGS_PACKAGE + '.' + \"TimingHistoryEntry\";\n    private static final String DATA_CLASS = TIMINGS_PACKAGE + '.' + \"TimingData\";\n\n    private static final ChatColor HEADER_COLOR = ChatColor.YELLOW;\n\n    private int historyInterval;\n\n    public PaperTimingsCommand(LagMonitor plugin) {\n        super(plugin);\n\n        try {\n            historyInterval = Reflection.getField(\"com.destroystokyo.paper.PaperConfig\", \"config\"\n            , YamlConfiguration.class).get(null).getInt(\"timings.history-interval\");\n        } catch (IllegalArgumentException illegalArgumentException) {\n            //cannot find paper spigot\n            historyInterval = -1;\n        }\n    }\n\n    @Override\n    protected boolean isTimingsEnabled() {\n        return Timings.isTimingsEnabled();\n    }\n\n    @Override\n    protected void sendTimings(CommandSender sender) {\n        EvictingQueue<TimingHistory> history = Reflection.getField(TimingsManager.class, \"HISTORY\", EvictingQueue.class)\n                .get(null);\n\n        TimingHistory lastHistory = history.peek();\n        if (lastHistory == null) {\n            sendError(sender, \"Not enough data collected yet. You need to wait at least 5min after server startup\");\n            return;\n        }\n\n        List<BaseComponent[]> lines = new ArrayList<>();\n        printTimings(lines, lastHistory);\n\n        Pages pagination = new Pages(\"Paper Timings\", lines);\n        pagination.send(sender);\n\n        plugin.getPageManager().setPagination(sender.getName(), pagination);\n    }\n\n    public void printTimings(Collection<BaseComponent[]> lines, TimingHistory lastHistory) {\n        printHeadData(lastHistory, lines);\n\n        Map<Integer, String> idHandler = Maps.newHashMap();\n\n        Map<?, ?> groups = Reflection.getField(TIMINGS_PACKAGE + \".TimingIdentifier\", \"GROUP_MAP\", Map.class).get(null);\n        for (Object group : groups.values()) {\n            String groupName = Reflection.getField(group.getClass(), \"name\", String.class).get(group);\n            Iterable<?> handlers = Reflection.getField(group.getClass(), \"handlers\", List.class).get(group);\n            for (Object handler : handlers) {\n                int id = Reflection.getField(HANDLER_CLASS, \"id\", Integer.TYPE).get(handler);\n                Object identifier = Reflection.getField(HANDLER_CLASS, \"identifier\", Object.class).get(handler);\n                String name = Reflection.getField(identifier.getClass(), \"name\", String.class).get(identifier);\n                if (name.contains(\"Combined\")) {\n                    idHandler.put(id, \"Combined \" + groupName);\n                } else {\n                    idHandler.put(id, name);\n                }\n            }\n        }\n\n        //TimingHistoryEntry\n        Object[] entries = Reflection.getField(TimingHistory.class, \"entries\", Object[].class).get(lastHistory);\n        for (Object entry : entries) {\n            Object parentData = Reflection.getField(HISTORY_ENTRY_CLASS, \"data\", Object.class).get(entry);\n            int childId = Reflection.getField(DATA_CLASS, \"id\", Integer.TYPE).get(parentData);\n\n            String handlerName = idHandler.get(childId);\n            String parentName;\n            if (handlerName == null) {\n                parentName = \"Unknown-\" + childId;\n            } else {\n                parentName = handlerName;\n            }\n\n            int parentCount = Reflection.getField(DATA_CLASS, \"count\", Integer.TYPE).get(parentData);\n            long parentTime = Reflection.getField(DATA_CLASS, \"totalTime\", Long.TYPE).get(parentData);\n\n//            long parentLagCount = Reflection.getField(DATA_CLASS, \"lagCount\", Integer.TYPE).get(parentData);\n//            long parentLagTime = Reflection.getField(DATA_CLASS, \"lagTime\", Long.TYPE).get(parentData);\n            lines.add(new ComponentBuilder(parentName).color(HEADER_COLOR)\n                    .append(\" Count: \" + parentCount + \" Time: \" + parentTime).create());\n\n            Object[] children = Reflection.getField(HISTORY_ENTRY_CLASS, \"children\", Object[].class).get(entry);\n            for (Object childData : children) {\n                printChildren(parentData, childData, idHandler, lines);\n            }\n        }\n    }\n\n    private void printChildren(Object parent, Object childData, Map<Integer, String> idMap,\n                               Collection<BaseComponent[]> lines) {\n        int childId = Reflection.getField(DATA_CLASS, \"id\", Integer.TYPE).get(childData);\n\n        String handlerName = idMap.get(childId);\n        String childName;\n        if (handlerName == null) {\n            childName = \"Unknown-\" + childId;\n        } else {\n            childName = handlerName;\n        }\n\n        int childCount = Reflection.getField(DATA_CLASS, \"count\", Integer.TYPE).get(childData);\n        long childTime = Reflection.getField(DATA_CLASS, \"totalTime\", Long.TYPE).get(childData);\n\n        long parentTime = Reflection.getField(DATA_CLASS, \"totalTime\", Long.TYPE).get(parent);\n        double percent = (double) childTime / parentTime;\n\n        lines.add(new ComponentBuilder(\"    \" + childName + \" Count: \" + childCount + \" Time: \" + childTime\n                + ' ' + round(percent) + '%')\n                .color(PRIMARY_COLOR.asBungee()).create());\n    }\n\n    private void printHeadData(TimingHistory lastHistory, Collection<BaseComponent[]> lines) {\n        // Represents all time spent running the server this history\n        long totalTime = Reflection.getField(TimingHistory.class, \"totalTime\", Long.TYPE).get(lastHistory);\n        long totalTicks = Reflection.getField(TimingHistory.class, \"totalTicks\", Long.TYPE).get(lastHistory);\n\n        long cost = (long) Reflection.getMethod(EXPORT_CLASS, \"getCost\").invoke(null);\n        lines.add(new ComponentBuilder(\"Cost: \")\n                .color(PRIMARY_COLOR.asBungee())\n                .append(Long.toString(cost)).color(SECONDARY_COLOR.asBungee()).create());\n\n        double totalSeconds = (double) totalTime / 1000 / 1000;\n\n        long playerTicks = TimingHistory.playerTicks;\n        long tileEntityTicks = TimingHistory.tileEntityTicks;\n        long activatedEntityTicks = TimingHistory.activatedEntityTicks;\n        long entityTicks = TimingHistory.entityTicks;\n\n        double activatedAvgEntities = (double) activatedEntityTicks / totalTicks;\n        double totalAvgEntities = (double) entityTicks / totalTicks;\n\n        double averagePlayers = (double) playerTicks / totalTicks;\n\n        double desiredTicks = 20 * historyInterval;\n        double averageTicks = totalTicks / desiredTicks * 20;\n\n        String format = ChatColor.DARK_AQUA + \"%s\" + ' ' + ChatColor.GRAY + \"%s\";\n\n        //head data\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"Total (sec):\", round(totalSeconds))));\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"Ticks:\", round(totalTicks))));\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"Avg ticks:\", round(averageTicks))));\n//        lines.add(TextComponent.fromLegacyText(String.format(format, \"Server Load:\", round(serverLoad))));\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"AVG Players:\", round(averagePlayers))));\n\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"Activated Entities:\", round(activatedAvgEntities))\n                + \" / \" + round(totalAvgEntities)));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/timing/SpigotTimingsCommand.java",
    "content": "package com.github.games647.lagmonitor.command.timing;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.Pages;\nimport com.github.games647.lagmonitor.traffic.Reflection;\nimport com.github.games647.lagmonitor.traffic.Reflection.FieldAccessor;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.concurrent.TimeUnit;\n\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport net.md_5.bungee.api.chat.TextComponent;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.command.defaults.TimingsCommand;\nimport org.spigotmc.CustomTimingsHandler;\n\nimport static com.github.games647.lagmonitor.util.LagUtils.round;\n\n/**\n * Parsed from the PHP project by aikar\n * https://github.com/aikar/timings\n */\npublic class SpigotTimingsCommand extends TimingCommand {\n\n    //these timings will be in the breakdown report\n    private static final String EXCLUDE_IDENTIFIER = \"** \";\n    //TODO: Change to MethodHandles\n    public SpigotTimingsCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    protected boolean isTimingsEnabled() {\n        return Bukkit.getPluginManager().useTimings();\n    }\n\n    @Override\n    protected void sendTimings(CommandSender sender) {\n        //place sampleTime here to be very accurate\n        long sampleTime = System.nanoTime() - TimingsCommand.timingStart;\n        if (TimeUnit.NANOSECONDS.toMinutes(sampleTime) <= 5) {\n            sendError(sender, \"Sampling time is too low\");\n            return;\n        }\n\n        Queue<CustomTimingsHandler> handlers = Reflection.getField(CustomTimingsHandler.class, \"HANDLERS\", Queue.class)\n                .get(null);\n\n        List<BaseComponent[]> lines = new ArrayList<>();\n        sendParsedOutput(handlers, lines, sampleTime);\n\n        Pages pagination = new Pages(\"Paper Timings\", lines);\n        pagination.send(sender);\n\n        this.plugin.getPageManager().setPagination(sender.getName(), pagination);\n    }\n\n    private void sendParsedOutput(Iterable<CustomTimingsHandler> handlers, Collection<BaseComponent[]> lines,\n                                  long sampleTime) {\n        Map<String, Timing> timings = new HashMap<>();\n        Timing breakdownTiming = new Timing(\"Breakdown\", -1, -1);\n        Timing minecraftTiming = new Timing(\"Minecraft\");\n        timings.put(\"Minecraft\", minecraftTiming);\n        timings.put(\"Breakdown\", breakdownTiming);\n\n        parseTimings(handlers, timings, minecraftTiming, breakdownTiming);\n\n        long playerTicks = 0;\n        long activatedEntityTicks = 0;\n        long entityTicks = 0;\n        long numTicks = 0;\n        for (Map.Entry<String, Timing> entry : breakdownTiming.getSubCategories().entrySet()) {\n            String key = entry.getKey();\n            Timing value = entry.getValue();\n            if (\"** tickEntity - EntityPlayer\".equalsIgnoreCase(key)) {\n                playerTicks = value.getTotalCount();\n            } else if (\"** activatedTickEntity\".equalsIgnoreCase(key)) {\n                activatedEntityTicks = value.getTotalCount();\n            } else if (\"** tickEntity\".equalsIgnoreCase(key)) {\n                entityTicks = value.getTotalCount();\n            } else if (key.contains(\" - entityTick\")) {\n                numTicks = Math.max(numTicks, value.getTotalCount());\n            }\n        }\n\n        double serverLoad = 0;\n\n        for (Map.Entry<String, Timing> entry : timings.entrySet()) {\n            String category = entry.getKey();\n            Timing timing = entry.getValue();\n            float pct = (float) timing.getTotalTime() / sampleTime * 100;\n\n            String highlightedPercent = highlightPct(round(pct), 1, 3, 6);\n            if (timing == minecraftTiming) {\n                highlightedPercent = highlightPct(round(pct), 20, 40, 70);\n            }\n\n            //nanoseconds -> seconds\n            float totalSeconds = (float) timing.getTotalTime() / 1000 / 1000 / 1000;\n            lines.add(TextComponent.fromLegacyText(ChatColor.YELLOW + \"=== \" + category\n                    + \" Total: \" + round(totalSeconds) + \"sec: \"\n                    + highlightedPercent + \"% \" + ChatColor.YELLOW + \"===\"));\n            if (timing.getSubCategories() != null) {\n                for (Map.Entry<String, Timing> subEntry : timing.getSubCategories().entrySet()) {\n                    String event = subEntry.getKey().replace(\"** \", \"\").replace(\"-\", \"\");\n                    int lastPackage = event.lastIndexOf('.');\n                    if (lastPackage != -1) {\n                        event = event.substring(lastPackage + 1);\n                    }\n\n                    Timing subValue = subEntry.getValue();\n\n                    double avg = subValue.calculateAverage();\n                    double timesPerTick = (double) subValue.getTotalCount() / numTicks;\n                    if (timesPerTick > 1) {\n                        avg *= timesPerTick;\n                    }\n\n                    double pctTick = avg / 1000 / 1000 / 50 * 100;\n//                    float count = (float) subValue.getTotalCount() / 1000;\n                    //->ms\n                    avg = avg / 1000 / 1000;\n                    double pctTotal = (double) subValue.getTotalTime() / sampleTime * 100;\n                    if (\"Full Server Tick\".equalsIgnoreCase(event)) {\n                        serverLoad = pctTick;\n                    }\n\n                    lines.add(TextComponent.fromLegacyText(ChatColor.DARK_AQUA + event + ' '\n                            + highlightPct(round(pctTotal), 10, 20, 50)\n                            + \" Tick: \" + highlightPct(round(pctTick), 3, 15, 40)\n                            + \" AVG: \" + round(avg) + \"ms\"));\n                }\n            }\n        }\n\n        lines.add(new ComponentBuilder(\"==========================================\").color(ChatColor.GOLD).create());\n\n        long total = minecraftTiming.getTotalTime();\n        printHeadData(total, activatedEntityTicks, numTicks, entityTicks, playerTicks, sampleTime, lines, serverLoad);\n    }\n\n    private void printHeadData(long total, long activatedEntityTicks, long numTicks, long entityTicks, long playerTicks\n            , long sampleTime, Collection<BaseComponent[]> lines, double serverLoad) {\n        float totalSeconds = (float) total / 1000 / 1000 / 1000;\n\n        float activatedAvgEntities = (float) activatedEntityTicks / numTicks;\n        float totalAvgEntities = (float) entityTicks / numTicks;\n\n        float averagePlayers = (float) playerTicks / numTicks;\n\n        float desiredTicks = (float) sampleTime / 1000 / 1000 / 1000 * 20;\n        float averageTicks = numTicks / desiredTicks * 20;\n\n        String format = ChatColor.DARK_AQUA + \"%s\" + ' ' + ChatColor.GRAY + \"%s\";\n\n        //head data\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"Total (sec):\", round(totalSeconds))));\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"Ticks:\", round(numTicks))));\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"Avg ticks:\", round(averageTicks))));\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"Server Load:\", round(serverLoad))));\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"AVG Players:\", round(averagePlayers))));\n\n        lines.add(TextComponent.fromLegacyText(String.format(format, \"Activated Entities:\", round(activatedAvgEntities))\n                + \" / \" + round(totalAvgEntities)));\n\n        //convert from nanoseconds to seconds\n        String formatted = String.format(format, \"Sample Time (sec):\", round((float) sampleTime / 1000 / 1000 / 1000));\n        lines.add(TextComponent.fromLegacyText(formatted));\n    }\n\n    private void parseTimings(Iterable<CustomTimingsHandler> handlers, Map<String, Timing> timings\n            , Timing minecraftTiming, Timing breakdownTiming) {\n//        FieldAccessor<CustomTimingsHandler> getParent = Reflection\n//                .getField(CustomTimingsHandler.class, \"parent\", CustomTimingsHandler.class);\n        FieldAccessor<String> getName = Reflection.getField(CustomTimingsHandler.class, \"name\", String.class);\n\n        FieldAccessor<Long> getTotalTime = Reflection.getField(CustomTimingsHandler.class, \"totalTime\", Long.TYPE);\n        FieldAccessor<Long> getCount = Reflection.getField(CustomTimingsHandler.class, \"count\", Long.TYPE);\n//        FieldAccessor<Long> getViolations = Reflection.getField(CustomTimingsHandler.class, \"violations\", Long.TYPE);\n        for (CustomTimingsHandler handler : handlers) {\n            String subCategory = getName.get(handler);\n            long totalTime = getTotalTime.get(handler);\n            long count = getCount.get(handler);\n\n            Timing active = minecraftTiming;\n            if (subCategory.contains(\"Event: \")) {\n                String pluginName = getProperty(subCategory, \"Plugin\");\n                subCategory = getProperty(subCategory, \"Event\");\n\n                active = timings.computeIfAbsent(pluginName, Timing::new);\n            } else if (subCategory.contains(\"Task: \")) {\n                String pluginName = getProperty(subCategory, \"Task\");\n                subCategory = getProperty(subCategory, \"Runnable\");\n\n                active = timings.computeIfAbsent(pluginName, Timing::new);\n            }\n\n            if (subCategory.startsWith(EXCLUDE_IDENTIFIER)) {\n                breakdownTiming.addSubcategory(subCategory, totalTime, count);\n            } else {\n                active.addSubcategory(subCategory, totalTime, count);\n                if (subCategory.startsWith(\"Task:\")) {\n                    breakdownTiming.addSubcategory(EXCLUDE_IDENTIFIER + \"Tasks\", totalTime, count);\n                }\n\n                if (active.getTotalTime() >= 0) {\n                    active.addTotal(totalTime);\n                }\n            }\n        }\n    }\n\n    private String getProperty(String line, String propertyName) {\n        String categoryName = propertyName + \": \";\n\n        int startIndex = line.indexOf(categoryName) + categoryName.length();\n        int endIndex = line.indexOf(' ', startIndex);\n        if (endIndex == -1) {\n            //line reached the end\n            endIndex = line.length();\n        }\n\n        return line.substring(startIndex, endIndex);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/timing/Timing.java",
    "content": "package com.github.games647.lagmonitor.command.timing;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\npublic class Timing implements Comparable<Timing> {\n\n    private final String category;\n\n    private long totalTime;\n    private long totalCount;\n\n    private Map<String, Timing> subcategories;\n\n    public Timing(String category) {\n        this.category = category;\n    }\n\n    public Timing(String category, long totalTime, long count) {\n        this.category = category;\n        this.totalTime = totalTime;\n        this.totalCount = count;\n    }\n\n    public String getCategoryName() {\n        return category;\n    }\n\n    public long getTotalTime() {\n        return totalTime;\n    }\n\n    public void addTotal(long total) {\n        this.totalTime += total;\n    }\n\n    public long getTotalCount() {\n        return totalCount;\n    }\n\n    public void addCount(long count) {\n        this.totalCount += count;\n    }\n\n    public double calculateAverage() {\n        if (totalCount == 0) {\n            return 0;\n        }\n\n        return (double) totalTime / totalCount;\n    }\n\n    public Map<String, Timing> getSubCategories() {\n        return subcategories;\n    }\n\n    public void addSubcategory(String name, long totalTime, long count) {\n        if (subcategories == null) {\n            //lazy creating\n            subcategories = new HashMap<>();\n        }\n\n        Timing timing = subcategories.computeIfAbsent(name, key -> new Timing(key, totalTime, count));\n        timing.addTotal(totalTime);\n        timing.addCount(totalTime);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        Timing timing = (Timing) o;\n        return totalTime == timing.totalTime &&\n                totalCount == timing.totalCount &&\n                Objects.equals(category, timing.category) &&\n                Objects.equals(subcategories, timing.subcategories);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(category, totalTime, totalCount, subcategories);\n    }\n\n    @Override\n    public int compareTo(Timing other) {\n        return Long.compare(totalTime, other.totalTime);\n    }\n\n    @Override\n    public String toString() {\n        return this.getClass().getSimpleName() + '{' +\n                \"category='\" + category + '\\'' +\n                \", totalTime=\" + totalTime +\n                \", totalCount=\" + totalCount +\n                \", subcategories=\" + subcategories +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/command/timing/TimingCommand.java",
    "content": "package com.github.games647.lagmonitor.command.timing;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.command.LagCommand;\n\nimport net.md_5.bungee.api.ChatColor;\n\nimport org.bukkit.command.Command;\nimport org.bukkit.command.CommandSender;\n\npublic abstract class TimingCommand extends LagCommand {\n\n    public TimingCommand(LagMonitor plugin) {\n        super(plugin);\n    }\n\n    @Override\n    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {\n        if (!canExecute(sender, command)) {\n            return true;\n        }\n\n        if (!isTimingsEnabled()) {\n            sendError(sender,\"The server deactivated timing reports\");\n            sendError(sender,\"Go to paper.yml or spigot.yml and activate timings\");\n            return true;\n        }\n\n        sendTimings(sender);\n        return true;\n    }\n\n    protected abstract void sendTimings(CommandSender sender);\n\n    protected abstract boolean isTimingsEnabled();\n\n    protected String highlightPct(float percent, int low, int med, int high) {\n        ChatColor prefix = ChatColor.GRAY;\n        if (percent > high) {\n            prefix = ChatColor.DARK_RED;\n        } else if (percent > med) {\n            prefix = ChatColor.GOLD;\n        } else if (percent > low) {\n            prefix = ChatColor.YELLOW;\n        }\n\n        return prefix + String.valueOf(percent) + '%' + ChatColor.GRAY;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/ClassesGraph.java",
    "content": "package com.github.games647.lagmonitor.graph;\n\nimport java.lang.management.ClassLoadingMXBean;\nimport java.lang.management.ManagementFactory;\n\nimport org.bukkit.map.MapCanvas;\n\npublic class ClassesGraph extends GraphRenderer {\n\n    private final ClassLoadingMXBean classBean = ManagementFactory.getClassLoadingMXBean();\n\n    public ClassesGraph() {\n        super(\"Classes\");\n    }\n\n    @Override\n    public int renderGraphTick(MapCanvas canvas, int nextPosX) {\n        int loadedClasses = classBean.getLoadedClassCount();\n\n        //round up to the nearest multiple of 5\n        int roundedMax = (int) (5 * (Math.ceil((float) loadedClasses / 5)));\n        int loadedHeight = getHeightScaled(roundedMax, loadedClasses);\n\n        fillBar(canvas, nextPosX, MAX_HEIGHT - loadedHeight, MAX_COLOR);\n\n        //these is the max number\n        return loadedClasses;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/CombinedGraph.java",
    "content": "package com.github.games647.lagmonitor.graph;\n\nimport org.bukkit.map.MapCanvas;\n\npublic class CombinedGraph extends GraphRenderer {\n\n    private static final int SPACES = 2;\n\n    private final GraphRenderer[] graphRenderers;\n\n    private final int componentWidth;\n    private final int[] componentLastPos;\n\n    public CombinedGraph(GraphRenderer... renderers) {\n        super(\"Combined\");\n\n        this.graphRenderers = renderers;\n        this.componentLastPos = new int[graphRenderers.length];\n\n        //MAX width - spaces between (length - 1) the components\n        componentWidth = MAX_WIDTH - (SPACES * (graphRenderers.length - 1)) / graphRenderers.length;\n        for (int i = 0; i < componentLastPos.length; i++) {\n            componentLastPos[i] = i * componentWidth + i * SPACES;\n        }\n    }\n\n    @Override\n    public int renderGraphTick(MapCanvas canvas, int nextPosX) {\n        for (int i = 0; i < graphRenderers.length; i++) {\n            GraphRenderer graphRenderer = graphRenderers[i];\n            int position = this.componentLastPos[i];\n            position++;\n\n            //index starts with 0 so in the end - 1\n            int maxComponentWidth = (i + 1) * componentWidth + i * SPACES - 1;\n            if (position > maxComponentWidth) {\n                //reset it to the start pos\n                position = i * componentWidth + i * SPACES;\n            }\n\n            graphRenderer.renderGraphTick(canvas, position);\n            this.componentLastPos[i] = position;\n        }\n\n        return 100;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/CpuGraph.java",
    "content": "package com.github.games647.lagmonitor.graph;\n\nimport com.github.games647.lagmonitor.NativeManager;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.map.MapCanvas;\nimport org.bukkit.plugin.Plugin;\n\npublic class CpuGraph extends GraphRenderer {\n\n    private final Plugin plugin;\n    private final NativeManager nativeData;\n\n    private final Object lock = new Object();\n\n    private int systemHeight;\n    private int processHeight;\n\n    public CpuGraph(Plugin plugin, NativeManager nativeData) {\n        super(\"CPU Usage\");\n\n        this.plugin = plugin;\n        this.nativeData = nativeData;\n    }\n\n    @Override\n    public int renderGraphTick(MapCanvas canvas, int nextPosX) {\n        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {\n            int systemLoad = (int) (nativeData.getCPULoad() * 100);\n            int processLOad = (int) (nativeData.getProcessCPULoad() * 100);\n\n            int localSystemHeight = getHeightScaled(100, systemLoad);\n            int localProcessHeight = getHeightScaled(100, processLOad);\n\n            //flush updates\n            synchronized (lock) {\n                this.systemHeight = localSystemHeight;\n                this.processHeight = localProcessHeight;\n            }\n        });\n\n        //read it only one time\n        int localSystemHeight;\n        int localProcessHeight;\n        synchronized (lock) {\n            localSystemHeight = this.systemHeight;\n            localProcessHeight = this.processHeight;\n        }\n\n        fillBar(canvas, nextPosX, MAX_HEIGHT - localSystemHeight, MAX_COLOR);\n        fillBar(canvas, nextPosX, MAX_HEIGHT - localProcessHeight, USED_COLOR);\n\n        //set max height as 100%\n        return 100;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/GraphRenderer.java",
    "content": "package com.github.games647.lagmonitor.graph;\n\nimport org.bukkit.entity.Player;\nimport org.bukkit.map.MapCanvas;\nimport org.bukkit.map.MapPalette;\nimport org.bukkit.map.MapRenderer;\nimport org.bukkit.map.MapView;\nimport org.bukkit.map.MinecraftFont;\n\npublic abstract class GraphRenderer extends MapRenderer {\n\n    protected static final int TEXT_HEIGHT = MinecraftFont.Font.getHeight();\n\n    //max height and width = 128 (index from 0-127)\n    protected static final int MAX_WIDTH = 128;\n    protected static final int MAX_HEIGHT = 128;\n\n    //orange\n    protected static final byte MAX_COLOR = MapPalette.matchColor(235, 171, 96);\n\n    //blue\n    protected static final byte USED_COLOR = MapPalette.matchColor(105, 182, 212);\n\n    private int nextUpdate;\n    private int nextPosX;\n\n    private final String title;\n\n    public GraphRenderer(String title) {\n        this.title = title;\n    }\n\n    @Override\n    public void render(MapView map, MapCanvas canvas, Player player) {\n        if (nextUpdate <= 0) {\n            //paint only every half seconds (20 Ticks / 2)\n            nextUpdate = 10;\n\n            if (nextPosX >= MAX_WIDTH) {\n                //start again from the beginning\n                nextPosX = 0;\n            }\n\n            clearBar(canvas, nextPosX);\n            //make it more visual where the renderer is at the moment\n            clearBar(canvas, nextPosX + 1);\n            int maxValue = renderGraphTick(canvas, nextPosX);\n\n            //override the color\n            drawText(canvas, MAX_WIDTH / 2, MAX_HEIGHT / 2, title);\n\n            //count indicators\n            String maxText = Integer.toString(maxValue);\n            drawText(canvas, MAX_WIDTH - Math.floorDiv(getTextWidth(maxText), 2), TEXT_HEIGHT, maxText);\n\n            String midText = Integer.toString(maxValue / 2);\n            drawText(canvas, MAX_WIDTH - Math.floorDiv(getTextWidth(midText), 2), MAX_HEIGHT / 2, midText);\n\n            String zeroText = Integer.toString(0);\n            drawText(canvas, MAX_WIDTH - Math.floorDiv(getTextWidth(zeroText), 2), MAX_HEIGHT, zeroText);\n\n            nextPosX++;\n        }\n\n        nextUpdate--;\n    }\n\n    public abstract int renderGraphTick(MapCanvas canvas, int nextPosX);\n\n    protected int getHeightScaled(int maxValue, int value) {\n        return MAX_HEIGHT * value / maxValue;\n    }\n\n    protected void clearBar(MapCanvas canvas, int posX) {\n        //resets the complete y coordinates on this x in order to free unused\n        for (int yPos = 0; yPos < MAX_HEIGHT; yPos++) {\n            canvas.setPixel(posX, yPos, (byte) 0);\n        }\n    }\n\n    protected void clearMap(MapCanvas canvas) {\n        for (int xPos = 0; xPos < MAX_WIDTH; xPos++) {\n            fillBar(canvas, xPos, 0, (byte) 0);\n        }\n    }\n\n    protected void fillBar(MapCanvas canvas, int xPos, int yStart, byte color) {\n        for (int yPos = yStart; yPos < MAX_HEIGHT; yPos++) {\n            canvas.setPixel(xPos, yPos, color);\n        }\n    }\n\n    protected void drawText(MapCanvas canvas, int midX, int midY, String text) {\n        int textWidth = getTextWidth(text);\n        canvas.drawText(midX - (textWidth / 2), midY - (TEXT_HEIGHT / 2), MinecraftFont.Font, text);\n    }\n\n    private int getTextWidth(String text) {\n        return MinecraftFont.Font.getWidth(text);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/HeapGraph.java",
    "content": "package com.github.games647.lagmonitor.graph;\n\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.MemoryMXBean;\n\nimport org.bukkit.map.MapCanvas;\n\npublic class HeapGraph extends GraphRenderer {\n\n    private final MemoryMXBean heapUsage = ManagementFactory.getMemoryMXBean();\n\n    public HeapGraph() {\n        super(\"HeapUsage (MB)\");\n    }\n\n    @Override\n    public int renderGraphTick(MapCanvas canvas, int nextPosX) {\n        //byte -> mega byte\n        int max = (int) (heapUsage.getHeapMemoryUsage().getCommitted() / 1024 / 1024);\n        int used = (int) (heapUsage.getHeapMemoryUsage().getUsed() / 1024 / 1024);\n\n        //round to the next 100 e.g. 801 -> 900\n        int roundedMax = ((max + 99) / 100) * 100;\n\n        int maxHeight = getHeightScaled(roundedMax, max);\n        int usedHeight = getHeightScaled(roundedMax, used);\n\n        //x=0 y=0 is the left top point so convert it\n        int convertedMaxHeight = MAX_HEIGHT - maxHeight;\n        int convertedUsedHeight = MAX_HEIGHT - usedHeight;\n\n        fillBar(canvas, nextPosX, convertedMaxHeight, MAX_COLOR);\n        fillBar(canvas, nextPosX, convertedUsedHeight, USED_COLOR);\n\n        return maxHeight;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/graph/ThreadsGraph.java",
    "content": "package com.github.games647.lagmonitor.graph;\n\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.ThreadMXBean;\n\nimport org.bukkit.map.MapCanvas;\n\npublic class ThreadsGraph extends GraphRenderer {\n\n    private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();\n\n    public ThreadsGraph() {\n        super(\"Thread activity\");\n    }\n\n    @Override\n    public int renderGraphTick(MapCanvas canvas, int nextPosX) {\n        int threadCount = threadBean.getThreadCount();\n        int daemonCount = threadBean.getDaemonThreadCount();\n\n        //round up to the nearest multiple of 5\n        int roundedMax = (int) (5 * (Math.ceil((float) threadCount / 5)));\n        int threadHeight = getHeightScaled(roundedMax, threadCount);\n        int daemonHeight = getHeightScaled(roundedMax, daemonCount);\n\n        fillBar(canvas, nextPosX, MAX_HEIGHT - threadHeight, MAX_COLOR);\n        fillBar(canvas, nextPosX, MAX_HEIGHT - daemonHeight, USED_COLOR);\n\n        //these is the max number of all threads\n        return threadCount;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/listener/BlockingConnectionSelector.java",
    "content": "package com.github.games647.lagmonitor.listener;\n\nimport com.github.games647.lagmonitor.threading.BlockingActionManager;\nimport com.github.games647.lagmonitor.threading.Injectable;\n\nimport java.io.IOException;\nimport java.net.Proxy;\nimport java.net.ProxySelector;\nimport java.net.SocketAddress;\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.regex.Pattern;\n\npublic class BlockingConnectionSelector extends ProxySelector implements Injectable {\n\n    private static final Pattern WWW_PATERN = Pattern.compile(\"www\", Pattern.LITERAL);\n\n    private final BlockingActionManager actionManager;\n    private ProxySelector oldProxySelector;\n\n    public BlockingConnectionSelector(BlockingActionManager actionManager) {\n        this.actionManager = actionManager;\n    }\n\n    @Override\n    public List<Proxy> select(URI uri) {\n        String url = WWW_PATERN.matcher(uri.toString()).replaceAll(\"\");\n        if (uri.getScheme().startsWith(\"http\") || (uri.getPort() != 80 && uri.getPort() != 443)) {\n            actionManager.checkBlockingAction(\"Socket: \" + url);\n        }\n\n        return oldProxySelector == null ? Collections.singletonList(Proxy.NO_PROXY) : oldProxySelector.select(uri);\n    }\n\n    @Override\n    public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {\n        if (oldProxySelector != null) {\n            oldProxySelector.connectFailed(uri, sa, ioe);\n        }\n    }\n\n    @Override\n    public void inject() {\n        ProxySelector proxySelector = ProxySelector.getDefault();\n        if (proxySelector != this) {\n            oldProxySelector = proxySelector;\n            ProxySelector.setDefault(this);\n        }\n    }\n\n    @Override\n    public void restore() {\n        if (ProxySelector.getDefault() == this) {\n            ProxySelector.setDefault(oldProxySelector);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/listener/GraphListener.java",
    "content": "package com.github.games647.lagmonitor.listener;\n\nimport com.github.games647.lagmonitor.graph.GraphRenderer;\nimport com.github.games647.lagmonitor.util.LagUtils;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.Material;\nimport org.bukkit.entity.Item;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.EventPriority;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.PlayerDropItemEvent;\nimport org.bukkit.event.player.PlayerInteractEvent;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.inventory.PlayerInventory;\nimport org.bukkit.inventory.meta.ItemMeta;\nimport org.bukkit.inventory.meta.MapMeta;\nimport org.bukkit.map.MapView;\n\npublic class GraphListener implements Listener {\n\n    private final boolean mainHandSupported;\n\n    public GraphListener() {\n        boolean mainHandMethodEx = false;\n        try {\n            MethodType type = MethodType.methodType(ItemStack.class);\n            MethodHandles.publicLookup().findVirtual(PlayerInventory.class, \"getItemInMainHand\", type);\n            mainHandMethodEx = true;\n        } catch (ReflectiveOperationException notFoundEx) {\n            //default to false\n        }\n\n        this.mainHandSupported = mainHandMethodEx;\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)\n    public void onInteract(PlayerInteractEvent clickEvent) {\n        Player player = clickEvent.getPlayer();\n        PlayerInventory inventory = player.getInventory();\n\n        ItemStack mainHandItem;\n        if (mainHandSupported) {\n            mainHandItem = inventory.getItemInMainHand();\n        } else {\n            mainHandItem = inventory.getItemInHand();\n        }\n\n        if (isOurGraph(mainHandItem)) {\n            inventory.setItemInMainHand(new ItemStack(Material.AIR));\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)\n    public void onDrop(PlayerDropItemEvent dropItemEvent) {\n        Item itemDrop = dropItemEvent.getItemDrop();\n        ItemStack mapItem = itemDrop.getItemStack();\n        if (isOurGraph(mapItem)) {\n            mapItem.setAmount(0);\n        }\n    }\n\n    private boolean isOurGraph(ItemStack item) {\n        if (!LagUtils.isFilledMapSupported()) {\n            return isOurGraphLegacy(item);\n        }\n\n        if (item.getType() != Material.FILLED_MAP) {\n            return false;\n        }\n\n        ItemMeta meta = item.getItemMeta();\n        if (!(meta instanceof MapMeta)) {\n            return false;\n        }\n\n        MapMeta mapMeta = (MapMeta) meta;\n        MapView mapView = mapMeta.getMapView();\n        return mapView != null && isOurRenderer(mapView);\n    }\n\n    private boolean isOurGraphLegacy(ItemStack mapItem) {\n        if (mapItem.getType() != Material.MAP)\n            return false;\n\n        short mapId = mapItem.getDurability();\n        MapView mapView = Bukkit.getMap(mapId);\n        return mapView != null && isOurRenderer(mapView);\n    }\n\n    private boolean isOurRenderer(MapView mapView) {\n        return mapView.getRenderers().stream()\n                .anyMatch(GraphRenderer.class::isInstance);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/listener/PageManager.java",
    "content": "package com.github.games647.lagmonitor.listener;\n\nimport com.github.games647.lagmonitor.Pages;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.PlayerQuitEvent;\n\npublic class PageManager implements Listener {\n\n    private final Map<String, Pages> pages = new HashMap<>();\n\n    @EventHandler\n    public void onPlayerQuit(PlayerQuitEvent quitEvent) {\n        pages.remove(quitEvent.getPlayer().getName());\n    }\n\n    public Pages getPagination(String username) {\n        return pages.get(username);\n    }\n\n    public void setPagination(String username, Pages pagination) {\n        pages.put(username, pagination);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/listener/ThreadSafetyListener.java",
    "content": "package com.github.games647.lagmonitor.listener;\n\nimport com.github.games647.lagmonitor.threading.BlockingActionManager;\n\nimport org.bukkit.event.Event;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.BlockFromToEvent;\nimport org.bukkit.event.block.BlockPhysicsEvent;\nimport org.bukkit.event.entity.CreatureSpawnEvent;\nimport org.bukkit.event.entity.EntitySpawnEvent;\nimport org.bukkit.event.entity.ItemSpawnEvent;\nimport org.bukkit.event.inventory.InventoryOpenEvent;\nimport org.bukkit.event.player.PlayerCommandPreprocessEvent;\nimport org.bukkit.event.player.PlayerItemHeldEvent;\nimport org.bukkit.event.player.PlayerJoinEvent;\nimport org.bukkit.event.player.PlayerMoveEvent;\nimport org.bukkit.event.player.PlayerQuitEvent;\nimport org.bukkit.event.player.PlayerTeleportEvent;\nimport org.bukkit.event.server.PluginDisableEvent;\nimport org.bukkit.event.server.PluginEnableEvent;\nimport org.bukkit.event.world.ChunkLoadEvent;\nimport org.bukkit.event.world.ChunkUnloadEvent;\nimport org.bukkit.event.world.SpawnChangeEvent;\nimport org.bukkit.event.world.WorldLoadEvent;\nimport org.bukkit.event.world.WorldSaveEvent;\nimport org.bukkit.event.world.WorldUnloadEvent;\n\n/**\n * We can listen to events which are intended to run sync to the main thread.\n * If those events are fired on a async task the operation was likely not thread-safe.\n */\npublic class ThreadSafetyListener implements Listener {\n\n    private final BlockingActionManager actionManager;\n\n    public ThreadSafetyListener(BlockingActionManager actionManager) {\n        this.actionManager = actionManager;\n    }\n\n    @EventHandler\n    public void onCommand(PlayerCommandPreprocessEvent commandEvent) {\n        checkSafety(commandEvent);\n    }\n\n    @EventHandler\n    public void onInventoryOpen(InventoryOpenEvent inventoryOpenEvent) {\n        checkSafety(inventoryOpenEvent);\n    }\n\n    @EventHandler\n    public void onPlayerMove(PlayerMoveEvent moveEvent) {\n        checkSafety(moveEvent);\n    }\n\n    @EventHandler\n    public void onPlayerTeleport(PlayerTeleportEvent teleportEvent) {\n        checkSafety(teleportEvent);\n    }\n\n    @EventHandler\n    public void onPlayerJoin(PlayerJoinEvent joinEvent) {\n        checkSafety(joinEvent);\n    }\n\n    @EventHandler\n    public void onPlayerQuit(PlayerQuitEvent quitEvent) {\n        checkSafety(quitEvent);\n    }\n\n    @EventHandler\n    public void onItemHeldChange(PlayerItemHeldEvent itemHeldEvent) {\n        checkSafety(itemHeldEvent);\n    }\n\n    @EventHandler\n    public void onBlockPhysics(BlockPhysicsEvent blockPhysicsEvent) {\n        checkSafety(blockPhysicsEvent);\n    }\n\n    @EventHandler\n    public void onBlockFromTo(BlockFromToEvent blockFromToEvent) {\n        checkSafety(blockFromToEvent);\n    }\n\n    @EventHandler\n    public void onCreatureSpawn(CreatureSpawnEvent creatureSpawnEvent) {\n        checkSafety(creatureSpawnEvent);\n    }\n\n    @EventHandler\n    public void onItemSpawn(ItemSpawnEvent itemSpawnEvent) {\n        checkSafety(itemSpawnEvent);\n    }\n\n    @EventHandler\n    public void onChunkLoad(ChunkLoadEvent chunkLoadEvent) {\n        checkSafety(chunkLoadEvent);\n    }\n\n    @EventHandler\n    public void onChunkUnload(ChunkUnloadEvent chunkUnloadEvent) {\n        checkSafety(chunkUnloadEvent);\n    }\n\n    @EventHandler\n    public void onWorldLoad(WorldLoadEvent worldLoadEvent) {\n        checkSafety(worldLoadEvent);\n    }\n\n    @EventHandler\n    public void onWorldSave(WorldSaveEvent worldSaveEvent) {\n        checkSafety(worldSaveEvent);\n    }\n\n    @EventHandler\n    public void onWorldUnload(WorldUnloadEvent worldUnloadEvent) {\n        checkSafety(worldUnloadEvent);\n    }\n\n    @EventHandler\n    public void onPluginEnable(PluginEnableEvent pluginEnableEvent) {\n        checkSafety(pluginEnableEvent);\n    }\n\n    @EventHandler\n    public void onPluginDisable(PluginDisableEvent pluginDisableEvent) {\n        checkSafety(pluginDisableEvent);\n    }\n\n    @EventHandler\n    public void onSpawnChange(SpawnChangeEvent spawnChangeEvent) {\n        checkSafety(spawnChangeEvent);\n    }\n\n    @EventHandler\n    public void onSpawnChange(EntitySpawnEvent spawnEvent) {\n        checkSafety(spawnEvent);\n    }\n\n    private void checkSafety(Event eventType) {\n        //async executing of sync event\n        String eventName = eventType.getEventName();\n        if (!eventType.isAsynchronous()) {\n            actionManager.checkThreadSafety(eventName);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/logging/ForwardLogService.java",
    "content": "package com.github.games647.lagmonitor.logging;\n\nimport org.slf4j.ILoggerFactory;\nimport org.slf4j.IMarkerFactory;\nimport org.slf4j.jul.JULServiceProvider;\nimport org.slf4j.spi.MDCAdapter;\nimport org.slf4j.spi.SLF4JServiceProvider;\n\npublic class ForwardLogService implements SLF4JServiceProvider {\n\n    private ILoggerFactory loggerFactory;\n    private JULServiceProvider delegate;\n\n    public ILoggerFactory getLoggerFactory() {\n        return this.loggerFactory;\n    }\n\n    public IMarkerFactory getMarkerFactory() {\n        return delegate.getMarkerFactory();\n    }\n\n    public MDCAdapter getMDCAdapter() {\n        return delegate.getMDCAdapter();\n    }\n\n    @Override\n    public String getRequesteApiVersion() {\n        return delegate.getRequestedApiVersion();\n    }\n\n    public String getRequestedApiVersion() {\n        return delegate.getRequestedApiVersion();\n    }\n\n    public void initialize() {\n        this.delegate = new JULServiceProvider();\n        this.loggerFactory = new ForwardingLoggerFactory();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/logging/ForwardingLoggerFactory.java",
    "content": "package com.github.games647.lagmonitor.logging;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\nimport org.slf4j.ILoggerFactory;\nimport org.slf4j.Logger;\nimport org.slf4j.jul.JDK14LoggerAdapter;\n\npublic class ForwardingLoggerFactory implements ILoggerFactory {\n\n    private final ConcurrentMap<String, Logger> loggerMap = new ConcurrentHashMap<>();\n\n    public static java.util.logging.Logger PARENT_LOGGER;\n\n    @Override\n    public Logger getLogger(String name) {\n        return loggerMap.computeIfAbsent(name, key -> {\n            java.util.logging.Logger julLogger;\n            if (PARENT_LOGGER == null) {\n                julLogger = java.util.logging.Logger.getLogger(name);\n            } else {\n                julLogger = PARENT_LOGGER;\n            }\n\n            Logger newInstance = null;\n            try {\n                newInstance = createJDKLogger(julLogger);\n            } catch (NoSuchMethodException | InvocationTargetException |\n                     InstantiationException | IllegalAccessException e) {\n                e.printStackTrace();\n                System.out.println(\"Failed to created logging instance\");\n            }\n\n            return newInstance;\n        });\n    }\n\n    protected static Logger createJDKLogger(java.util.logging.Logger parent)\n            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {\n        Class<?> adapterClass = JDK14LoggerAdapter.class;\n        Constructor<?> cons = adapterClass.getDeclaredConstructor(java.util.logging.Logger.class);\n        cons.setAccessible(true);\n        return (Logger) cons.newInstance(parent);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/ping/PaperPing.java",
    "content": "package com.github.games647.lagmonitor.ping;\n\nimport org.bukkit.entity.Player;\n\npublic class PaperPing implements PingFetcher {\n\n    @Override\n    public boolean isAvailable() {\n        try {\n            //Only available in Paper\n            Player.Spigot.class.getDeclaredMethod(\"getPing\");\n            return true;\n        } catch (NoSuchMethodException noSuchMethodEx) {\n            return false;\n        }\n    }\n\n    @Override\n    public int getPing(Player player) {\n        return player.spigot().getPing();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/ping/PingFetcher.java",
    "content": "package com.github.games647.lagmonitor.ping;\n\nimport org.bukkit.entity.Player;\n\npublic interface PingFetcher {\n\n    boolean isAvailable();\n\n    int getPing(Player player);\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/ping/ReflectionPing.java",
    "content": "package com.github.games647.lagmonitor.ping;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.traffic.Reflection;\n\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodHandles.Lookup;\nimport java.lang.invoke.MethodType;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nimport org.bukkit.entity.Player;\nimport org.bukkit.plugin.java.JavaPlugin;\n\npublic class ReflectionPing implements PingFetcher {\n\n    private static final MethodHandle pingFromPlayerHandle;\n\n    static {\n        MethodHandle localPing = null;\n        Class<?> craftPlayerClass = Reflection.getCraftBukkitClass(\"entity.CraftPlayer\");\n        String playerClazz = \"EntityPlayer\";\n        Class<?> entityPlayer = Reflection.getMinecraftClass(playerClazz, \"level.\" + playerClazz);\n\n        Lookup lookup = MethodHandles.publicLookup();\n        try {\n            MethodType type = MethodType.methodType(entityPlayer);\n            MethodHandle getHandle = lookup.findVirtual(craftPlayerClass, \"getHandle\", type);\n            MethodHandle pingField = lookup.findGetter(entityPlayer, \"ping\", Integer.TYPE);\n\n            // combine the handles to invoke it only once\n            // *getPing(getHandle*) -> add the result of getHandle to the next getPing call\n            // a call to this handle will get the ping from a player instance\n            localPing = MethodHandles.collectArguments(pingField, 0, getHandle)\n                    // allow interface with invokeExact\n                    .asType(MethodType.methodType(int.class, Player.class));\n        } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException reflectiveEx) {\n            Logger logger = JavaPlugin.getPlugin(LagMonitor.class).getLogger();\n            logger.log(Level.WARNING, \"Cannot find ping field/method\", reflectiveEx);\n        }\n\n        pingFromPlayerHandle = localPing;\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return pingFromPlayerHandle != null;\n    }\n\n    @Override\n    public int getPing(Player player) {\n        try {\n            return (int) pingFromPlayerHandle.invokeExact(player);\n        } catch (Exception ex) {\n            return -1;\n        } catch (Throwable throwable) {\n            throw (Error) throwable;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/ping/SpigotPing.java",
    "content": "package com.github.games647.lagmonitor.ping;\n\nimport org.bukkit.entity.Player;\n\npublic class SpigotPing implements PingFetcher {\n\n    @Override\n    public boolean isAvailable() {\n        try {\n            //Only available in Paper\n            Player.class.getDeclaredMethod(\"getPing\");\n            return true;\n        } catch (NoSuchMethodException noSuchMethodEx) {\n            return false;\n        }\n    }\n\n    @Override\n    public int getPing(Player player) {\n        return player.getPing();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/MonitorSaveTask.java",
    "content": "package com.github.games647.lagmonitor.storage;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.NativeManager;\nimport com.github.games647.lagmonitor.util.LagUtils;\nimport com.google.common.collect.Lists;\nimport com.google.common.collect.Maps;\n\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.OperatingSystemMXBean;\nimport java.nio.file.Path;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.logging.Level;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.World;\nimport org.bukkit.entity.Player;\n\nimport static com.github.games647.lagmonitor.util.LagUtils.round;\n\npublic class MonitorSaveTask implements Runnable {\n\n    protected final LagMonitor plugin;\n    protected final Storage storage;\n\n    public MonitorSaveTask(LagMonitor plugin, Storage storage) {\n        this.plugin = plugin;\n        this.storage = storage;\n    }\n\n    @Override\n    public void run() {\n        try {\n            int monitorId = save();\n            if (monitorId == -1) {\n                //error occurred\n                return;\n            }\n\n            Map<UUID, WorldData> worldsData = getWorldData();\n            if (!storage.saveWorlds(monitorId, worldsData.values())) {\n                //error occurred\n                return;\n            }\n\n            List<PlayerData> playerData = getPlayerData(worldsData);\n            storage.savePlayers(playerData);\n        } catch (ExecutionException | InterruptedException ex) {\n            plugin.getLogger().log(Level.SEVERE, \"Error saving monitoring data\", ex);\n        }\n    }\n\n    private List<PlayerData> getPlayerData(final Map<UUID, WorldData> worldsData)\n            throws InterruptedException, ExecutionException {\n        Future<List<PlayerData>> playerFuture = Bukkit.getScheduler()\n                .callSyncMethod(plugin, () -> {\n                    Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();\n                    List<PlayerData> playerData = Lists.newArrayListWithCapacity(onlinePlayers.size());\n                    for (Player player : onlinePlayers) {\n                        UUID worldId = player.getWorld().getUID();\n\n                        int worldRowId = 0;\n                        WorldData worldData = worldsData.get(worldId);\n                        if (worldData != null) {\n                            worldRowId = worldData.getRowId();\n                        }\n\n                        String name = player.getName();\n                        int lastPing = plugin.getPingManager().map(m -> ((int) m.getHistory(name).getLastSample()))\n                                .orElse(-1);\n\n                        UUID playerId = player.getUniqueId();\n                        playerData.add(new PlayerData(worldRowId, playerId, name, lastPing));\n                    }\n\n                    return playerData;\n                });\n        \n        return playerFuture.get();\n    }\n\n    private Map<UUID, WorldData> getWorldData()\n            throws ExecutionException, InterruptedException {\n        //this is not thread-safe and have to run sync\n\n        Future<Map<UUID, WorldData>> worldFuture = Bukkit.getScheduler()\n                .callSyncMethod(plugin, () -> {\n                            List<World> worlds = Bukkit.getWorlds();\n                            Map<UUID, WorldData> worldsData = Maps.newHashMapWithExpectedSize(worlds.size());\n                            for (World world : worlds) {\n                                worldsData.put(world.getUID(), WorldData.fromWorld(world));\n                            }\n\n                            return worldsData;\n                        });\n\n        Map<UUID, WorldData> worldsData = worldFuture.get();\n\n        //this can run async because it's thread-safe\n        worldsData.values().parallelStream()\n                .forEach(data -> {\n                    Path worldFolder = Bukkit.getWorld(data.getWorldName()).getWorldFolder().toPath();\n\n                    int worldSize = LagUtils.byteToMega(LagUtils.getFolderSize(plugin.getLogger(), worldFolder));\n                    data.setWorldSize(worldSize);\n                });\n\n        return worldsData;\n    }\n\n    private int save() {\n        Runtime runtime = Runtime.getRuntime();\n        int maxMemory = LagUtils.byteToMega(runtime.maxMemory());\n        //we need the free ram not the free heap\n        int usedRam = LagUtils.byteToMega(runtime.totalMemory() - runtime.freeMemory());\n        int freeRam = maxMemory - usedRam;\n\n        float freeRamPct = round((freeRam * 100) / maxMemory, 4);\n\n        OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();\n        float loadAvg = round(osBean.getSystemLoadAverage(), 4);\n        if (loadAvg < 0) {\n            //windows doesn't support this\n            loadAvg = 0;\n        }\n\n        NativeManager nativeData = plugin.getNativeData();\n        float systemUsage = round(nativeData.getCPULoad() * 100, 4);\n        float processUsage = round(nativeData.getProcessCPULoad() * 100, 4);\n\n        int totalOsMemory = LagUtils.byteToMega(nativeData.getTotalMemory());\n        int freeOsRam = LagUtils.byteToMega(nativeData.getFreeMemory());\n\n        float freeOsRamPct = round((freeOsRam * 100) / totalOsMemory, 4);\n        return storage.saveMonitor(processUsage, systemUsage, freeRam, freeRamPct, freeOsRam, freeOsRamPct, loadAvg);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/NativeSaveTask.java",
    "content": "package com.github.games647.lagmonitor.storage;\n\nimport com.github.games647.lagmonitor.LagMonitor;\nimport com.github.games647.lagmonitor.traffic.TrafficReader;\nimport com.github.games647.lagmonitor.util.LagUtils;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Optional;\n\nimport oshi.SystemInfo;\nimport oshi.hardware.NetworkIF;\nimport oshi.software.os.OSProcess;\n\nimport static com.github.games647.lagmonitor.util.LagUtils.round;\n\npublic class NativeSaveTask implements Runnable {\n\n    private final LagMonitor plugin;\n    private final Storage storage;\n\n    private Instant lastCheck = Instant.now();\n\n    private int lastMcRead;\n    private int lastMcWrite;\n    private int lastDiskRead;\n    private int lastDiskWrite;\n    private int lastNetRead;\n    private int lastNetWrite;\n\n    public NativeSaveTask(LagMonitor plugin, Storage storage) {\n        this.plugin = plugin;\n        this.storage = storage;\n    }\n\n    @Override\n    public void run() {\n        Instant currentTime = Instant.now();\n        int timeDiff = (int) Duration.between(lastCheck, currentTime).getSeconds();\n\n        int mcReadDiff = 0;\n        int mcWriteDiff = 0;\n\n        TrafficReader trafficReader = plugin.getTrafficReader();\n        if (trafficReader != null) {\n            int mcRead = LagUtils.byteToMega(trafficReader.getIncomingBytes().longValue());\n            mcReadDiff = getDifference(mcRead, lastMcRead, timeDiff);\n            lastMcRead = mcRead;\n\n            int mcWrite = LagUtils.byteToMega(trafficReader.getOutgoingBytes().longValue());\n            mcWriteDiff = getDifference(mcWrite, lastMcWrite, timeDiff);\n            lastMcWrite = mcWrite;\n        }\n\n        int totalSpace = LagUtils.byteToMega(plugin.getNativeData().getTotalSpace());\n        int freeSpace = LagUtils.byteToMega(plugin.getNativeData().getFreeSpace());\n\n        //4 decimal places -> Example: 0.2456\n        float freeSpacePct = round((freeSpace * 100 / (float) totalSpace), 4);\n\n        int diskReadDiff = 0;\n        int diskWriteDiff = 0;\n        int netReadDiff = 0;\n        int netWriteDiff = 0;\n\n        Optional<SystemInfo> systemInfo = plugin.getNativeData().getSystemInfo();\n        if (systemInfo.isPresent()) {\n            List<NetworkIF> networkIfs = systemInfo.get().getHardware().getNetworkIFs();\n            if (!networkIfs.isEmpty()) {\n                NetworkIF networkInterface = networkIfs.get(0);\n\n                int netRead = LagUtils.byteToMega(networkInterface.getBytesRecv());\n                netReadDiff = getDifference(netRead, lastNetRead, timeDiff);\n                lastNetRead = netRead;\n\n                int netWrite = LagUtils.byteToMega(networkInterface.getBytesSent());\n                netWriteDiff = getDifference(netWrite, lastNetWrite, timeDiff);\n                lastNetWrite = netWrite;\n            }\n\n            Path root = Paths.get(\".\").getRoot();\n            Optional<OSProcess> optProcess = plugin.getNativeData().getProcess();\n            if (root != null && optProcess.isPresent()) {\n                OSProcess process = optProcess.get();\n                String rootFileSystem = root.toAbsolutePath().toString();\n\n                int diskRead = LagUtils.byteToMega(process.getBytesRead());\n                diskReadDiff = getDifference(diskRead, lastDiskRead, timeDiff);\n                lastDiskRead = diskRead;\n\n                int diskWrite = LagUtils.byteToMega(process.getBytesWritten());\n                diskWriteDiff = getDifference(diskWrite, lastDiskWrite, timeDiff);\n                lastDiskWrite = diskWrite;\n            }\n        }\n\n        lastCheck = currentTime;\n        storage.saveNative(mcReadDiff, mcWriteDiff, freeSpace, freeSpacePct, diskReadDiff, diskWriteDiff\n                , netReadDiff, netWriteDiff);\n    }\n\n    private int getDifference(long newVal, long oldVal, long timeDiff) {\n        return (int) ((newVal - oldVal) / timeDiff);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/PlayerData.java",
    "content": "package com.github.games647.lagmonitor.storage;\n\nimport java.util.UUID;\n\npublic class PlayerData {\n\n    private final int worldId;\n    private final UUID uuid;\n    private final String playerName;\n    private final int ping;\n\n    public PlayerData(int worldId, UUID uuid, String playerName, int ping) {\n        this.worldId = worldId;\n        this.uuid = uuid;\n        this.playerName = playerName;\n\n        if (ping < 0) {\n            this.ping = Integer.MAX_VALUE;\n        } else {\n            this.ping = ping;\n        }\n    }\n\n    public int getWorldId() {\n        return worldId;\n    }\n\n    public UUID getUuid() {\n        return uuid;\n    }\n\n    public String getPlayerName() {\n        return playerName;\n    }\n\n    public int getPing() {\n        return ping;\n    }\n\n    @Override\n    public String toString() {\n        return this.getClass().getSimpleName() + '{' +\n                \"worldId=\" + worldId\n                + \", uuid=\" + uuid\n                + \", playerName=\" + playerName\n                + \", ping=\" + ping\n                + '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/Storage.java",
    "content": "package com.github.games647.lagmonitor.storage;\n\nimport com.google.common.collect.Lists;\nimport com.mysql.cj.jdbc.MysqlDataSource;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Collection;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\npublic class Storage {\n\n    private static final String TPS_TABLE = \"tps\";\n    private static final String PLAYERS_TABLE = \"players\";\n    private static final String MONITOR_TABLE = \"monitor\";\n    private static final String WORLDS_TABLE = \"worlds\";\n    private static final String NATIVE_TABLE = \"native\";\n\n    private final MysqlDataSource dataSource;\n\n    private final Logger logger;\n    private final String prefix;\n\n    public Storage(Logger logger, String host, int port, String database, boolean usessl,\n                   String user, String pass, String prefix) {\n        this.logger = logger;\n\n        this.prefix = prefix;\n\n        this.dataSource = new MysqlDataSource();\n        this.dataSource.setUser(user);\n        this.dataSource.setPassword(pass);\n\n        this.dataSource.setServerName(host);\n        this.dataSource.setPort(port);\n        this.dataSource.setDatabaseName(database);\n\n        tryFeature(dataSource, source -> source.setUseSSL(usessl), \"Failed to configure use ssl - using to default\");\n        tryFeature(dataSource, source -> source.setCachePrepStmts(true), \"Failed to enable caching of statements\");\n        tryFeature(dataSource, source -> source.setUseServerPrepStmts(true), \"Failed to enable server caching\");\n    }\n\n    @FunctionalInterface\n    private interface FeatureTester {\n        void run(MysqlDataSource dataSource) throws SQLException;\n    }\n\n    private void tryFeature(MysqlDataSource dataSource, FeatureTester task, String errorMsg) {\n        try {\n            task.run(dataSource);\n        } catch (SQLException sqlEx) {\n            this.logger.log(Level.WARNING, errorMsg, sqlEx);\n        }\n    }\n\n    public void createTables() throws SQLException {\n        try (InputStream in = getClass().getResourceAsStream(\"/create.sql\");\n             BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));\n             Connection con = dataSource.getConnection();\n             Statement stmt = con.createStatement()) {\n            StringBuilder builder = new StringBuilder();\n\n            String line;\n            while ((line = reader.readLine()) != null) {\n                if (line.startsWith(\"#\")) continue;\n\n                builder.append(line);\n                if (line.endsWith(\";\")) {\n                    stmt.addBatch(builder.toString().replace(\"{prefix}\", prefix));\n                    builder = new StringBuilder();\n                }\n            }\n\n            stmt.executeBatch();\n        } catch (IOException ioEx) {\n            logger.log(Level.SEVERE, \"Failed to load migration file\", ioEx);\n        }\n    }\n\n    public int saveMonitor(float procUsage, float osUsage, int freeRam, float freeRamPct, int osRam, float osRamPct\n            , float loadAvg) {\n        try (Connection con = dataSource.getConnection();\n             PreparedStatement stmt = con.prepareStatement(\"INSERT INTO \" + prefix + MONITOR_TABLE\n                     + \" (process_usage, os_usage, free_ram, free_ram_pct, os_free_ram, os_free_ram_pct, load_avg)\"\n                     + \" VALUES (?, ?, ?, ?, ?, ?, ?)\", Statement.RETURN_GENERATED_KEYS)) {\n            stmt.setFloat(1, procUsage);\n            stmt.setFloat(2, osUsage);\n            stmt.setInt(3, freeRam);\n            stmt.setFloat(4, freeRamPct);\n            stmt.setInt(5, osRam);\n            stmt.setFloat(6, osRamPct);\n            stmt.setFloat(7, loadAvg);\n            stmt.execute();\n\n            try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {\n                if (generatedKeys.next()) {\n                    return generatedKeys.getInt(1);\n                }\n            }\n        } catch (SQLException sqlEx) {\n            logger.log(Level.SEVERE, \"Error saving monitor data to database\", sqlEx);\n            logger.log(Level.SEVERE, \"Using this data {0}\"\n                    , Lists.newArrayList(procUsage, osUsage, freeRam, freeRamPct, osRam, osRamPct, loadAvg));\n        }\n\n        return -1;\n    }\n\n    public boolean saveWorlds(int monitorId, Collection<WorldData> worldsData) {\n        if (worldsData.isEmpty()) {\n            return false;\n        }\n\n        try (Connection con = dataSource.getConnection();\n             PreparedStatement stmt = con.prepareStatement(\"INSERT INTO \" + prefix + WORLDS_TABLE\n                     + \" (monitor_id, world_name, chunks_loaded, tile_entities, entities, world_size)\"\n                     + \" VALUES (?, ?, ?, ?, ?, ?)\", Statement.RETURN_GENERATED_KEYS)) {\n            for (WorldData worldData : worldsData) {\n                stmt.setInt(1, monitorId);\n                stmt.setString(2, worldData.getWorldName());\n                stmt.setInt(3, worldData.getLoadedChunks());\n                stmt.setInt(4, worldData.getTileEntities());\n                stmt.setInt(5, worldData.getEntities());\n                stmt.setInt(6, worldData.getWorldSize());\n                stmt.addBatch();\n            }\n\n            stmt.executeBatch();\n\n            try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {\n                for (WorldData worldData : worldsData) {\n                    if (generatedKeys.next()) {\n                        worldData.setRowId(generatedKeys.getInt(1));\n                    }\n                }\n            }\n\n            return true;\n        } catch (SQLException sqlEx) {\n            logger.log(Level.SEVERE, \"Error saving worlds data to database\", sqlEx);\n            logger.log(Level.SEVERE, \"Using this data {0}\", worldsData);\n        }\n\n        return false;\n    }\n\n    public void savePlayers(Collection<PlayerData> playerData) {\n        if (playerData.isEmpty()) {\n            return;\n        }\n\n        try (Connection con = dataSource.getConnection();\n             PreparedStatement stmt = con.prepareStatement(\"INSERT INTO \" + prefix + PLAYERS_TABLE\n                     + \" (world_id, uuid, name, ping) \"\n                     + \"VALUES (?, ?, ?, ?)\")) {\n            for (PlayerData data : playerData) {\n                stmt.setInt(1, data.getWorldId());\n                stmt.setString(2, data.getUuid().toString());\n                stmt.setString(3, data.getPlayerName());\n                stmt.setInt(4, data.getPing());\n                stmt.addBatch();\n            }\n\n            stmt.executeBatch();\n        } catch (SQLException sqlEx) {\n            logger.log(Level.SEVERE, \"Error saving player data to database\", sqlEx);\n            logger.log(Level.SEVERE, \"Using this data {0}\", playerData);\n        }\n\n    }\n\n    public void saveNative(int mcRead, int mcWrite, int freeSpace, float freePct, int diskRead, int diskWrite\n            , int netRead, int netWrite) {\n        try (Connection con = dataSource.getConnection();\n             PreparedStatement stmt = con.prepareStatement(\"INSERT INTO \" + prefix + NATIVE_TABLE\n                     + \" (mc_read, mc_write, free_space, free_space_pct, disk_read, disk_write, net_read, net_write)\"\n                     + \" VALUES (?, ?, ?, ?, ?, ?, ?, ?)\")) {\n            stmt.setInt(1, mcRead);\n            stmt.setInt(2, mcWrite);\n\n            stmt.setInt(3, freeSpace);\n\n            stmt.setFloat(4, freePct);\n\n            stmt.setInt(5, diskRead);\n            stmt.setInt(6, diskWrite);\n\n            stmt.setInt(7, netRead);\n            stmt.setInt(8, netWrite);\n            stmt.execute();\n        } catch (SQLException sqlEx) {\n            logger.log(Level.SEVERE, \"Error saving native stats to database\", sqlEx);\n            logger.log(Level.SEVERE, \"Using this data {0}\"\n                    , Lists.newArrayList(mcRead, mcWrite, freeSpace, freePct, diskRead, diskWrite, netRead, netWrite));\n        }\n    }\n\n    public void saveTps(float tps) {\n        try (Connection con = dataSource.getConnection();\n             PreparedStatement stmt = con.prepareStatement(\"INSERT INTO \" + prefix\n                     + TPS_TABLE + \" (tps) VALUES (?)\")) {\n            stmt.setFloat(1, tps);\n            stmt.execute();\n        } catch (SQLException sqlEx) {\n            logger.log(Level.SEVERE, \"Error saving tps to database\", sqlEx);\n            logger.log(Level.SEVERE, \"Using this data {0}\", new Object[]{tps});\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/TPSSaveTask.java",
    "content": "package com.github.games647.lagmonitor.storage;\n\nimport com.github.games647.lagmonitor.task.TPSHistoryTask;\n\npublic class TPSSaveTask implements Runnable {\n\n    private final TPSHistoryTask tpsHistoryTask;\n    private final Storage storage;\n\n    public TPSSaveTask(TPSHistoryTask tpsHistoryTask, Storage storage) {\n        this.tpsHistoryTask = tpsHistoryTask;\n        this.storage = storage;\n    }\n\n    @Override\n    public void run() {\n        float lastSample = tpsHistoryTask.getLastSample();\n        if (lastSample > 0 && lastSample < 50) {\n            storage.saveTps(lastSample);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/storage/WorldData.java",
    "content": "package com.github.games647.lagmonitor.storage;\n\nimport org.bukkit.Chunk;\nimport org.bukkit.World;\n\npublic class WorldData {\n\n    private final String worldName;\n    private final int loadedChunks;\n    private final int tileEntities;\n    private final int entities;\n\n    private int worldSize;\n    private int rowId;\n\n    public static WorldData fromWorld(World world) {\n        String worldName = world.getName();\n        int tileEntities = 0;\n        for (Chunk loadedChunk : world.getLoadedChunks()) {\n            tileEntities += loadedChunk.getTileEntities().length;\n        }\n\n        int entities = world.getEntities().size();\n        int chunks = world.getLoadedChunks().length;\n\n        return new WorldData(worldName, chunks, tileEntities, entities);\n    }\n\n    public WorldData(String worldName, int loadedChunks, int tileEntities, int entities) {\n        this.worldName = worldName;\n        this.loadedChunks = loadedChunks;\n        this.tileEntities = tileEntities;\n        this.entities = entities;\n    }\n\n    public String getWorldName() {\n        return worldName;\n    }\n\n    public int getLoadedChunks() {\n        return loadedChunks;\n    }\n\n    public int getTileEntities() {\n        return tileEntities;\n    }\n\n    public int getEntities() {\n        return entities;\n    }\n\n    public int getWorldSize() {\n        return worldSize;\n    }\n\n    public void setWorldSize(int worldSize) {\n        this.worldSize = worldSize;\n    }\n\n    public int getRowId() {\n        return rowId;\n    }\n\n    public void setRowId(int rowId) {\n        this.rowId = rowId;\n    }\n\n    @Override\n    public String toString() {\n        return this.getClass().getSimpleName() + '{' +\n                \"worldName=\" + worldName\n                + \", loadedChunks=\" + loadedChunks\n                + \", tileEntities=\" + tileEntities\n                + \", entities=\" + entities\n                + \", worldSize=\" + worldSize\n                + \", rowId=\" + rowId\n                + '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/task/IODetectorTask.java",
    "content": "package com.github.games647.lagmonitor.task;\n\nimport com.github.games647.lagmonitor.threading.BlockingActionManager;\n\nimport java.lang.Thread.State;\nimport java.util.TimerTask;\n\npublic class IODetectorTask extends TimerTask {\n\n    private final BlockingActionManager actionManager;\n    private final Thread mainThread;\n\n    public IODetectorTask(BlockingActionManager actionManager, Thread mainThread) {\n        this.actionManager = actionManager;\n        this.mainThread = mainThread;\n    }\n\n    @Override\n    public void run() {\n        //According to this post the thread is still in Runnable although it's waiting for\n        //file/http resources\n        //https://stackoverflow.com/questions/20795295/why-jstack-out-says-thread-state-is-runnable-while-socketread\n        if (mainThread.getState() == State.RUNNABLE) {\n            //Based on this post we have to check the top element of the stack\n            //https://stackoverflow.com/questions/20891386/how-to-detect-thread-being-blocked-by-io\n            StackTraceElement[] stackTrace = mainThread.getStackTrace();\n            StackTraceElement topElement = stackTrace[stackTrace.length - 1];\n            if (topElement.isNativeMethod()) {\n                //Socket/SQL (connect) - java.net.DualStackPlainSocketImpl.connect0\n                //Socket/SQL (read) - java.net.SocketInputStream.socketRead0\n                //Socket/SQL (write) - java.net.SocketOutputStream.socketWrite0\n                if (isElementEqual(topElement, \"java.net.DualStackPlainSocketImpl\", \"connect0\")\n                        || isElementEqual(topElement, \"java.net.SocketInputStream\", \"socketRead0\")\n                        || isElementEqual(topElement, \"java.net.SocketOutputStream\", \"socketWrite0\")) {\n                    actionManager.logCurrentStack(\"Server is performing {1} on the main thread. \"\n                            + \"Properly caused by {0}\", \"java.net.SocketStream\");\n                } //File (in) - java.io.FileInputStream.readBytes\n                //File (out) - java.io.FileOutputStream.writeBytes\n                else if (isElementEqual(topElement, \"java.io.FileInputStream\", \"readBytes\")\n                        || isElementEqual(topElement, \"java.io.FileOutputStream\", \"writeBytes\")) {\n                    actionManager.logCurrentStack(\"Server is performing {1} on the main thread. \" +\n                            \"Properly caused by {0}\", \"java.io.FileStream\");\n                }\n            }\n        }\n    }\n\n    private boolean isElementEqual(StackTraceElement traceElement, String className, String methodName) {\n        return traceElement.getClassName().equals(className) && traceElement.getMethodName().equals(methodName);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/task/MonitorTask.java",
    "content": "package com.github.games647.lagmonitor.task;\n\nimport com.github.games647.lagmonitor.MethodMeasurement;\nimport com.github.games647.lagmonitor.command.MonitorCommand;\nimport com.google.common.net.UrlEscapers;\nimport com.google.gson.Gson;\nimport com.google.gson.JsonObject;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStreamWriter;\nimport java.io.Reader;\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.ThreadInfo;\nimport java.lang.management.ThreadMXBean;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.TimerTask;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\n/**\n * Based on the project https://github.com/sk89q/WarmRoast by sk89q\n */\npublic class MonitorTask extends TimerTask {\n\n    private static final String PASTE_URL = \"https://paste.enginehub.org/paste\";\n    private static final int MAX_DEPTH = 25;\n\n    private final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();\n    private final Logger logger;\n    private final long threadId;\n\n    private MethodMeasurement rootNode;\n    private int samples;\n\n    public MonitorTask(Logger logger, long threadId) {\n        this.logger = logger;\n        this.threadId = threadId;\n    }\n\n    public synchronized MethodMeasurement getRootSample() {\n        return rootNode;\n    }\n\n    public synchronized int getSamples() {\n        return samples;\n    }\n\n    @Override\n    public void run() {\n        ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId, MAX_DEPTH);\n        StackTraceElement[] stackTrace = threadInfo.getStackTrace();\n        if (stackTrace.length > 0) {\n            StackTraceElement rootElement = stackTrace[stackTrace.length - 1];\n            synchronized (this) {\n                samples++;\n\n                if (rootNode == null) {\n                    String rootClass = rootElement.getClassName();\n                    String rootMethod = rootElement.getMethodName();\n\n                    String id = rootClass + '.' + rootMethod;\n                    rootNode = new MethodMeasurement(id, rootClass, rootMethod);\n                }\n\n                rootNode.onMeasurement(stackTrace, 0, MonitorCommand.SAMPLE_INTERVAL);\n            }\n        }\n    }\n\n    public String paste() {\n        try {\n            HttpURLConnection httpConnection = (HttpURLConnection) new URL(PASTE_URL).openConnection();\n            httpConnection.setRequestMethod(\"POST\");\n            httpConnection.setDoOutput(true);\n            httpConnection.setDoInput(true);\n\n            try (BufferedWriter writer = new BufferedWriter(\n                    new OutputStreamWriter(httpConnection.getOutputStream(), StandardCharsets.UTF_8))\n            ) {\n                writer.write(\"content=\" + UrlEscapers.urlPathSegmentEscaper().escape(toString()));\n                writer.write(\"&from=\" + logger.getName());\n            }\n\n            JsonObject object;\n            try (Reader reader = new BufferedReader(\n                    new InputStreamReader(httpConnection.getInputStream(), StandardCharsets.UTF_8))\n            ) {\n                object = new Gson().fromJson(reader, JsonObject.class);\n            }\n\n            if (object.has(\"url\")) {\n                return object.get(\"url\").getAsString();\n            }\n\n            logger.log(Level.INFO, \"Failed to parse url from {0}\", object);\n        } catch (IOException ex) {\n            logger.log(Level.SEVERE, \"Failed to upload monitoring data\", ex);\n        }\n\n        return null;\n    }\n\n    @Override\n    public String toString() {\n        ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId, MAX_DEPTH);\n\n        StringBuilder builder = new StringBuilder();\n        builder.append(threadInfo.getThreadName());\n        builder.append(' ');\n\n        synchronized (this) {\n            builder.append(rootNode.getTotalTime()).append(\"ms\");\n            builder.append('\\n');\n\n            rootNode.writeString(builder, 1);\n        }\n\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/task/PingManager.java",
    "content": "package com.github.games647.lagmonitor.task;\n\nimport com.github.games647.lagmonitor.ping.PaperPing;\nimport com.github.games647.lagmonitor.ping.PingFetcher;\nimport com.github.games647.lagmonitor.ping.ReflectionPing;\nimport com.github.games647.lagmonitor.ping.SpigotPing;\nimport com.github.games647.lagmonitor.util.RollingOverHistory;\nimport com.google.common.collect.Lists;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.PlayerJoinEvent;\nimport org.bukkit.event.player.PlayerQuitEvent;\nimport org.bukkit.plugin.Plugin;\n\npublic class PingManager implements Runnable, Listener {\n\n    //the server is pinging the client every 40 Ticks (2 sec) - so check it then\n    //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/PlayerConnection.java#L178\n    public static final int PING_INTERVAL = 2 * 20;\n    private static final int SAMPLE_SIZE = 5;\n\n    private final Map<String, RollingOverHistory> playerHistory = new HashMap<>();\n    private final Plugin plugin;\n    private final PingFetcher pingFetcher;\n\n    public PingManager(Plugin plugin) throws ReflectiveOperationException {\n        this.pingFetcher = initializePingFetchur();\n        this.plugin = plugin;\n    }\n\n    private PingFetcher initializePingFetchur()\n            throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {\n        List<Class<? extends PingFetcher>> fetchurs = Lists.newArrayList(\n                SpigotPing.class, PaperPing.class, ReflectionPing.class\n        );\n        for (Class<? extends PingFetcher> fetchurClass : fetchurs) {\n            PingFetcher fetchur = fetchurClass.getDeclaredConstructor().newInstance();\n            if (fetchur.isAvailable())\n                return fetchur;\n        }\n\n        throw new NoSuchMethodException(\"No valid ping fetcher found\");\n    }\n\n    @Override\n    public void run() {\n        playerHistory.forEach((playerName, history) -> {\n            Player player = Bukkit.getPlayerExact(playerName);\n            if (player != null) {\n                int ping = pingFetcher.getPing(player);\n                history.add(ping);\n            }\n        });\n    }\n\n    public RollingOverHistory getHistory(String playerName) {\n        return playerHistory.get(playerName);\n    }\n\n    public void addPlayer(Player player) {\n        int ping = pingFetcher.getPing(player);\n        playerHistory.put(player.getName(), new RollingOverHistory(SAMPLE_SIZE, ping));\n    }\n\n    public void removePlayer(Player player) {\n        playerHistory.remove(player.getName());\n    }\n\n    @EventHandler\n    public void onPlayerJoin(PlayerJoinEvent joinEvent) {\n        Player player = joinEvent.getPlayer();\n        Bukkit.getScheduler().runTaskLater(plugin, () -> {\n            if (player.isOnline()) {\n                addPlayer(player);\n            }\n        }, PING_INTERVAL);\n    }\n\n    @EventHandler\n    public void onPlayerQuit(PlayerQuitEvent quitEvent) {\n        removePlayer(quitEvent.getPlayer());\n    }\n\n    public void clear() {\n        playerHistory.clear();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/task/TPSHistoryTask.java",
    "content": "package com.github.games647.lagmonitor.task;\n\nimport com.github.games647.lagmonitor.util.RollingOverHistory;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class TPSHistoryTask implements Runnable {\n\n    public static final int RUN_INTERVAL = 20;\n    private static final int ONE_MINUTE = (int) TimeUnit.MINUTES.toSeconds(1);\n\n    private final RollingOverHistory minuteSample = new RollingOverHistory(ONE_MINUTE, 20.0F);\n    private final RollingOverHistory quarterSample = new RollingOverHistory(ONE_MINUTE * 15, 20.0F);\n    private final RollingOverHistory halfHourSample = new RollingOverHistory(ONE_MINUTE * 30, 20.0F);\n\n    //the last time we updated the ticks\n    private long lastCheck = System.nanoTime();\n\n    public RollingOverHistory getMinuteSample() {\n        return minuteSample;\n    }\n\n    public RollingOverHistory getQuarterSample() {\n        return quarterSample;\n    }\n\n    public RollingOverHistory getHalfHourSample() {\n        return halfHourSample;\n    }\n\n    public float getLastSample() {\n        synchronized (this) {\n            int lastPos = minuteSample.getCurrentPosition();\n            return minuteSample.getSamples()[lastPos];\n        }\n    }\n\n    @Override\n    public void run() {\n        //nanoTime is more accurate\n        long currentTime = System.nanoTime();\n        long timeSpent = currentTime - lastCheck;\n        //update the last check\n        lastCheck = currentTime;\n\n        //how many ticks passed since the last check * 1000 to convert to seconds\n        float tps = 1 * 20 * 1000.0F / (timeSpent / (1000 * 1000));\n        if (tps >= 0.0F && tps < 25.0F) {\n            //Prevent all invalid values\n            synchronized (this) {\n                minuteSample.add(tps);\n                quarterSample.add(tps);\n                halfHourSample.add(tps);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/threading/BlockingActionManager.java",
    "content": "package com.github.games647.lagmonitor.threading;\n\nimport com.google.common.collect.Maps;\nimport com.google.common.collect.Sets;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.logging.Level;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.event.Listener;\nimport org.bukkit.plugin.Plugin;\nimport org.bukkit.plugin.java.JavaPlugin;\n\npublic class BlockingActionManager implements Listener {\n\n    //feel free to improve the wording of this:\n    private static final String THREAD_SAFETY_NOTICE = \"As threads **can** run concurrently or in parallel \" +\n            \"shared data access has to be synchronized (thread-safety) in order to prevent \" +\n            \"unexpected behavior or crashes. \";\n\n    private static final String SAFETY_METHODS = \"You can guarantee thread-safety by \" +\n            \"running the data access always on the same thread, using atomic operations, \" +\n            \"locks (ex: a synchronized block), immutable objects, thread local data \" +\n            \"or something similar. \";\n\n    private static final String COMMON_SAFE = \"Common things that are thread-safe: Logging, Bukkit Scheduler, \" +\n            \"Concurrent collections (ex: ConcurrentHashMap or Collections.synchronized*), ... \";\n\n    private static final String BLOCKING_ACTION_MESSAGE = \"Plugin {0} is performing a blocking I/O operation ({1}) \" +\n            \"on the main thread. \" +\n            \"This could affect the server performance, because the thread pauses until it gets the response. \" +\n            \"Such operations should be performed asynchronous from the main thread. \" +\n            \"Besides gameplay performance it could also improve startup time. \" +\n            \"Keep in mind to keep the code thread-safe. \";\n\n    private final Plugin plugin;\n\n    private final Set<PluginViolation> violations = Sets.newConcurrentHashSet();\n    private final Set<String> violatedPlugins = Sets.newConcurrentHashSet();\n\n    public BlockingActionManager(Plugin plugin) {\n        this.plugin = plugin;\n    }\n\n    public void checkBlockingAction(String event) {\n        if (!Bukkit.isPrimaryThread()) {\n            return;\n        }\n\n        logCurrentStack(BLOCKING_ACTION_MESSAGE, event);\n    }\n\n    public void checkThreadSafety(String eventName) {\n        if (Bukkit.isPrimaryThread()) {\n            return;\n        }\n\n        logCurrentStack(\"Plugin {0} triggered an synchronous event {1} from an asynchronous Thread. \"\n            + THREAD_SAFETY_NOTICE\n            + \"Use runTask* (no Async*), scheduleSync* or callSyncMethod to run on the main thread.\", eventName);\n    }\n\n    public void logCurrentStack(String format, String eventName) {\n        IllegalAccessException stackTraceCreator = new IllegalAccessException();\n        StackTraceElement[] stackTrace = stackTraceCreator.getStackTrace();\n\n        Map.Entry<String, StackTraceElement> foundPlugin = findPlugin(stackTrace);\n\n        PluginViolation violation = new PluginViolation(eventName);\n        if (foundPlugin != null) {\n            String pluginName = foundPlugin.getKey();\n            violation = new PluginViolation(pluginName, foundPlugin.getValue(), eventName);\n            if (!violatedPlugins.add(violation.getPluginName()) && plugin.getConfig().getBoolean(\"oncePerPlugin\")) {\n                return;\n            }\n        }\n\n        if (!violations.add(violation)) {\n            return;\n        }\n\n        plugin.getLogger().log(Level.WARNING, format + \"Report it to the plugin author\"\n                , new Object[]{violation.getPluginName(), eventName});\n\n        if (plugin.getConfig().getBoolean(\"hideStacktrace\")) {\n            plugin.getLogger().log(Level.WARNING, \"Source: {0}, method {1}, line {2}\"\n                    , new Object[]{violation.getSourceFile(), violation.getMethodName(), violation.getLineNumber()});\n        } else {\n            plugin.getLogger().log(Level.WARNING, \"The following exception is not an error. \" +\n                    \"It's a hint for the plugin developers to find the source. \" +\n                    plugin.getName() + \" doesn't prevent this action. It just warns you about it. \", stackTraceCreator);\n        }\n    }\n\n    public Map.Entry<String, StackTraceElement> findPlugin(StackTraceElement[] stacktrace) {\n        boolean skipping = true;\n        for (StackTraceElement elem : stacktrace) {\n            try {\n                Class<?> clazz = Class.forName(elem.getClassName());\n                if (clazz.getName().endsWith(\"VanillaCommandWrapper\")) {\n                    //explicit use getName instead of SimpleName because getSimpleBinaryName causes a\n                    //StringIndexOutOfBoundsException for obfuscated plugins\n                    return Maps.immutableEntry(\"Vanilla\", elem);\n                }\n\n                Plugin plugin;\n                try {\n                    plugin = JavaPlugin.getProvidingPlugin(clazz);\n                    if (plugin == this.plugin) {\n                        continue;\n                    }\n\n                    return Maps.immutableEntry(plugin.getName(), elem);\n                } catch (IllegalArgumentException illegalArgumentEx) {\n                    //ignore\n                }\n            } catch (ClassNotFoundException ex) {\n                //if this class cannot be loaded then it could be something native so we ignore it\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/threading/BlockingSecurityManager.java",
    "content": "package com.github.games647.lagmonitor.threading;\n\nimport com.google.common.collect.ImmutableSet;\n\nimport java.io.FilePermission;\nimport java.security.Permission;\nimport java.util.Set;\n\npublic class BlockingSecurityManager extends SecurityManager implements Injectable {\n\n    private final BlockingActionManager actionManager;\n    private final Set<String> allowedFiles = ImmutableSet.of(\".jar\", \"session.lock\");\n\n    private SecurityManager delegate;\n\n    public BlockingSecurityManager(BlockingActionManager actionManager) {\n        this.actionManager = actionManager;\n    }\n\n    @Override\n    public void checkPermission(Permission perm, Object context) {\n        if (delegate != null) {\n            delegate.checkPermission(perm, context);\n        }\n\n        checkMainThreadOperation(perm);\n    }\n\n    @Override\n    public void checkPermission(Permission perm) {\n        if (delegate != null) {\n            delegate.checkPermission(perm);\n        }\n\n        checkMainThreadOperation(perm);\n    }\n\n    private void checkMainThreadOperation(Permission perm) {\n        if (isBlockingAction(perm)) {\n            actionManager.checkBlockingAction(\"Permission: \" + perm.getName());\n        }\n    }\n\n    private boolean isBlockingAction(Permission permission) {\n        String actions = permission.getActions();\n        return permission instanceof FilePermission\n                && actions.contains(\"read\")\n                && allowedFiles.stream().noneMatch(ignored -> permission.getName().contains(ignored));\n    }\n\n    @Override\n    public void inject() {\n        SecurityManager oldSecurityManager = System.getSecurityManager();\n        if (oldSecurityManager != this) {\n            this.delegate = oldSecurityManager;\n            System.setSecurityManager(this);\n        }\n    }\n\n    @Override\n    public void restore() {\n        if (System.getSecurityManager() == this) {\n            System.setSecurityManager(delegate);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/threading/Injectable.java",
    "content": "package com.github.games647.lagmonitor.threading;\n\npublic interface Injectable {\n\n    void inject();\n\n    void restore();\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/threading/PluginViolation.java",
    "content": "package com.github.games647.lagmonitor.threading;\n\nimport java.util.Objects;\n\npublic class PluginViolation {\n\n    private final String pluginName;\n    private final String sourceFile;\n    private final String methodName;\n    private final int lineNumber;\n\n    private final String event;\n\n    public PluginViolation(String pluginName, StackTraceElement stackTraceElement, String event) {\n        this.pluginName = pluginName;\n        this.sourceFile = stackTraceElement.getFileName();\n        this.methodName = stackTraceElement.getMethodName();\n        this.lineNumber = stackTraceElement.getLineNumber();\n\n        this.event = event;\n    }\n\n    public PluginViolation(String event) {\n        this.pluginName = \"Unknown\";\n        this.sourceFile = \"Unknown\";\n        this.methodName = \"Unknown\";\n        this.lineNumber = -1;\n\n        this.event = event;\n    }\n\n    public String getPluginName() {\n        return pluginName;\n    }\n\n    public String getSourceFile() {\n        return sourceFile;\n    }\n\n    public String getMethodName() {\n        return methodName;\n    }\n\n    public int getLineNumber() {\n        return lineNumber;\n    }\n\n    public String getEvent() {\n        return event;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(pluginName, sourceFile, methodName);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (!(obj instanceof PluginViolation)) {\n            return false;\n        }\n\n        PluginViolation other = (PluginViolation) obj;\n        return Objects.equals(pluginName, other.pluginName)\n                && Objects.equals(sourceFile, other.sourceFile)\n                && Objects.equals(methodName, other.methodName);\n    }\n\n    @Override\n    public String toString() {\n        return this.getClass().getSimpleName() + '{' +\n                \"pluginName='\" + pluginName + '\\'' +\n                \", sourceFile='\" + sourceFile + '\\'' +\n                \", methodName='\" + methodName + '\\'' +\n                \", lineNumber=\" + lineNumber +\n                \", event='\" + event + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/traffic/CleanUpTask.java",
    "content": "package com.github.games647.lagmonitor.traffic;\n\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelPipeline;\n\nimport java.util.NoSuchElementException;\n\npublic class CleanUpTask implements Runnable {\n\n    private final ChannelPipeline pipeline;\n    private final ChannelInboundHandlerAdapter serverChannelHandler;\n\n    public CleanUpTask(ChannelPipeline pipeline, ChannelInboundHandlerAdapter serverChannelHandler) {\n        this.pipeline = pipeline;\n        this.serverChannelHandler = serverChannelHandler;\n    }\n\n    @Override\n    public void run() {\n        try {\n            pipeline.remove(serverChannelHandler);\n        } catch (NoSuchElementException e) {\n            // That's fine\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/traffic/Reflection.java",
    "content": "package com.github.games647.lagmonitor.traffic;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport org.bukkit.Bukkit;\n\n/**\n * An utility class that simplifies reflection in Bukkit plugins.\n *\n * @author Kristian\n */\npublic final class Reflection {\n\n    /**\n     * An interface for invoking a specific constructor.\n     */\n    @FunctionalInterface\n    public interface ConstructorInvoker {\n\n        /**\n         * Invoke a constructor for a specific class.\n         *\n         * @param arguments - the arguments to pass to the constructor.\n         * @return The constructed object.\n         */\n        Object invoke(Object... arguments);\n    }\n\n    /**\n     * An interface for invoking a specific method.\n     */\n    @FunctionalInterface\n    public interface MethodInvoker {\n\n        /**\n         * Invoke a method on a specific target object.\n         *\n         * @param target - the target object, or NULL for a static method.\n         * @param arguments - the arguments to pass to the method.\n         * @return The return value, or NULL if is void.\n         */\n        Object invoke(Object target, Object... arguments);\n    }\n\n    /**\n     * An interface for retrieving the field content.\n     *\n     * @param <T> - field type.\n     */\n    public interface FieldAccessor<T> {\n\n        /**\n         * Retrieve the content of a field.\n         *\n         * @param target - the target object, or NULL for a static field.\n         * @return The value of the field.\n         */\n        T get(Object target);\n\n        /**\n         * Set the content of a field.\n         *\n         * @param target - the target object, or NULL for a static field.\n         * @param value - the new value of the field.\n         */\n        void set(Object target, Object value);\n\n        /**\n         * Determine if the given object has this field.\n         *\n         * @param target - the object to test.\n         * @return TRUE if it does, FALSE otherwise.\n         */\n        boolean hasField(Object target);\n    }\n\n    // Deduce the net.minecraft.server.v* package\n    private static final String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName();\n    private static final String NMS_PREFIX = \"net.minecraft.server\";\n    private static final String NMS_PREFIX_VERSIONED = OBC_PREFIX.replace(\"org.bukkit.craftbukkit\", NMS_PREFIX);\n    private static final String VERSION = OBC_PREFIX.replace(\"org.bukkit.craftbukkit\", \"\").replace(\".\", \"\");\n\n    // Variable replacement\n    private static final Pattern MATCH_VARIABLE = Pattern.compile(\"\\\\{([^}]+)}\");\n\n    private Reflection() {\n        // Seal class\n    }\n\n    /**\n     * Retrieve a field accessor for a specific field type and name.\n     *\n     * @param target - the target type.\n     * @param name - the name of the field, or NULL to ignore.\n     * @param fieldType - a compatible field type.\n     * @return The field accessor.\n     */\n    public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {\n        return getField(target, name, fieldType, 0);\n    }\n\n    /**\n     * Retrieve a field accessor for a specific field type and name.\n     *\n     * @param className - lookup name of the class, see {@link #getClass(String)}.\n     * @param name - the name of the field, or NULL to ignore.\n     * @param fieldType - a compatible field type.\n     * @return The field accessor.\n     */\n    public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {\n        return getField(getClass(className), name, fieldType, 0);\n    }\n\n    /**\n     * Retrieve a field accessor for a specific field type and name.\n     *\n     * @param target - the target type.\n     * @param fieldType - a compatible field type.\n     * @param index - the number of compatible fields to skip.\n     * @return The field accessor.\n     */\n    public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {\n        return getField(target, null, fieldType, index);\n    }\n\n    /**\n     * Retrieve a field accessor for a specific field type and name.\n     *\n     * @param className - lookup name of the class, see {@link #getClass(String)}.\n     * @param fieldType - a compatible field type.\n     * @param index - the number of compatible fields to skip.\n     * @return The field accessor.\n     */\n    public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {\n        return getField(getClass(className), fieldType, index);\n    }\n\n    // Common method\n    private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index) {\n        for (final Field field : target.getDeclaredFields()) {\n            if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) {\n                field.setAccessible(true);\n\n                // A function for retrieving a specific field value\n                return new FieldAccessor<T>() {\n\n                    @Override\n                    @SuppressWarnings(\"unchecked\")\n                    public T get(Object target) {\n                        try {\n                            return (T) field.get(target);\n                        } catch (IllegalAccessException e) {\n                            throw new RuntimeException(\"Cannot access reflection.\", e);\n                        }\n                    }\n\n                    @Override\n                    public void set(Object target, Object value) {\n                        try {\n                            field.set(target, value);\n                        } catch (IllegalAccessException e) {\n                            throw new RuntimeException(\"Cannot access reflection.\", e);\n                        }\n                    }\n\n                    @Override\n                    public boolean hasField(Object target) {\n                        // target instanceof DeclaringClass\n                        return field.getDeclaringClass().isAssignableFrom(target.getClass());\n                    }\n                };\n            }\n        }\n\n        // Search in parent classes\n        if (target.getSuperclass() != null) {\n            return getField(target.getSuperclass(), name, fieldType, index);\n        }\n\n        throw new IllegalArgumentException(\"Cannot find field with type \" + fieldType);\n    }\n\n    /**\n     * Search for the first publicly and privately defined method of the given name and parameter count.\n     *\n     * @param className - lookup name of the class, see {@link #getClass(String)}.\n     * @param methodName - the method name, or NULL to skip.\n     * @param params - the expected parameters.\n     * @return An object that invokes this specific method.\n     * @throws IllegalStateException If we cannot find this method.\n     */\n    public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) {\n        return getTypedMethod(getClass(className), methodName, null, params);\n    }\n\n    /**\n     * Search for the first publicly and privately defined method of the given name and parameter count.\n     *\n     * @param clazz - a class to start with.\n     * @param methodName - the method name, or NULL to skip.\n     * @param params - the expected parameters.\n     * @return An object that invokes this specific method.\n     * @throws IllegalStateException If we cannot find this method.\n     */\n    public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) {\n        return getTypedMethod(clazz, methodName, null, params);\n    }\n\n    /**\n     * Search for the first publicly and privately defined method of the given name and parameter count.\n     *\n     * @param clazz - a class to start with.\n     * @param methodName - the method name, or NULL to skip.\n     * @param returnType - the expected return type, or NULL to ignore.\n     * @param params - the expected parameters.\n     * @return An object that invokes this specific method.\n     * @throws IllegalStateException If we cannot find this method.\n     */\n    public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {\n        for (final Method method : clazz.getDeclaredMethods()) {\n            if ((methodName == null || method.getName().equals(methodName)) && (returnType == null) || method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), params)) {\n                method.setAccessible(true);\n\n                return (target, arguments) -> {\n                    try {\n                        return method.invoke(target, arguments);\n                    } catch (Exception e) {\n                        throw new RuntimeException(\"Cannot invoke method \" + method, e);\n                    }\n                };\n            }\n        }\n\n        // Search in every superclass\n        if (clazz.getSuperclass() != null) {\n            return getMethod(clazz.getSuperclass(), methodName, params);\n        }\n\n        throw new IllegalStateException(String.format(\"Unable to find method %s (%s).\", methodName, Arrays.asList(params)));\n    }\n\n    /**\n     * Search for the first publicly and privately defined constructor of the given name and parameter count.\n     *\n     * @param className - lookup name of the class, see {@link #getClass(String)}.\n     * @param params - the expected parameters.\n     * @return An object that invokes this constructor.\n     * @throws IllegalStateException If we cannot find this method.\n     */\n    public static ConstructorInvoker getConstructor(String className, Class<?>... params) {\n        return getConstructor(getClass(className), params);\n    }\n\n    /**\n     * Search for the first publicly and privately defined constructor of the given name and parameter count.\n     *\n     * @param clazz - a class to start with.\n     * @param params - the expected parameters.\n     * @return An object that invokes this constructor.\n     * @throws IllegalStateException If we cannot find this method.\n     */\n    public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) {\n        for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {\n            if (Arrays.equals(constructor.getParameterTypes(), params)) {\n                constructor.setAccessible(true);\n\n                return arguments -> {\n                    try {\n                        return constructor.newInstance(arguments);\n                    } catch (Exception e) {\n                        throw new RuntimeException(\"Cannot invoke constructor \" + constructor, e);\n                    }\n                };\n            }\n        }\n\n        throw new IllegalStateException(String.format(\"Unable to find constructor for %s (%s).\", clazz, Arrays.asList(params)));\n    }\n\n    /**\n     * Retrieve a class from its full name, without knowing its type on compile time.\n     * <p>\n     * This is useful when looking up fields by a NMS or OBC type.\n     * <p>\n     *\n     * @see Object#getClass()\n     * @param lookupName - the class name with variables.\n     * @return The class.\n     */\n    public static Class<Object> getUntypedClass(String lookupName) {\n        @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n        Class<Object> clazz = (Class) getClass(lookupName);\n        return clazz;\n    }\n\n    /**\n     * Retrieve a class from its full name.\n     * <p>\n     * Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:\n     * </p>\n     * <table border=\"1\">\n     * <tr>\n     * <th>Variable</th>\n     * <th>Content</th>\n     * </tr>\n     * <tr>\n     * <td>{nms}</td>\n     * <td>Actual package name of net.minecraft.server.VERSION</td>\n     * </tr>\n     * <tr>\n     * <td>{obc}</td>\n     * <td>Actual package name of org.bukkit.craftbukkit.VERSION</td>\n     * </tr>\n     * <tr>\n     * <td>{version}</td>\n     * <td>The current Minecraft package VERSION, if any.</td>\n     * </tr>\n     * </table>\n     *\n     * @param lookupName - the class name with variables.\n     * @return The looked up class.\n     * @throws IllegalArgumentException If a variable or class could not be found.\n     */\n    public static Class<?> getClass(String lookupName) {\n        return getCanonicalClass(expandVariables(lookupName));\n    }\n\n    /**\n     * Retrieve a class in the net.minecraft.server.VERSION.* package.\n     *\n     * @param name - the name of the class, excluding the package.\n     * @throws IllegalArgumentException If the class doesn't exist.\n     */\n    public static Class<?> getMinecraftClass(String name, String alias) {\n        try {\n            return Class.forName(NMS_PREFIX + '.' + alias);\n        } catch (ClassNotFoundException e) {\n            return getCanonicalClass(NMS_PREFIX_VERSIONED + '.' + name);\n        }\n    }\n\n    /**\n     * Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package.\n     *\n     * @param name - the name of the class, excluding the package.\n     * @throws IllegalArgumentException If the class doesn't exist.\n     */\n    public static Class<?> getCraftBukkitClass(String name) {\n        return getCanonicalClass(OBC_PREFIX + '.' + name);\n    }\n\n    /**\n     * Retrieve a class by its canonical name.\n     *\n     * @param canonicalName - the canonical name.\n     * @return The class.\n     */\n    private static Class<?> getCanonicalClass(String canonicalName) {\n        try {\n            return Class.forName(canonicalName);\n        } catch (ClassNotFoundException e) {\n            throw new IllegalArgumentException(\"Cannot find \" + canonicalName, e);\n        }\n    }\n\n    /**\n     * Expand variables such as \"{nms}\" and \"{obc}\" to their corresponding packages.\n     *\n     * @param name - the full name of the class.\n     * @return The expanded string.\n     */\n    private static String expandVariables(String name) {\n        StringBuffer output = new StringBuffer();\n        Matcher matcher = MATCH_VARIABLE.matcher(name);\n\n        while (matcher.find()) {\n            String variable = matcher.group(1);\n            String replacement;\n\n            // Expand all detected variables\n            if (\"nms\".equalsIgnoreCase(variable)) {\n                replacement = NMS_PREFIX_VERSIONED;\n            } else if (\"obc\".equalsIgnoreCase(variable)) {\n                replacement = OBC_PREFIX;\n            } else if (\"version\".equalsIgnoreCase(variable)) {\n                replacement = VERSION;\n            } else {\n                throw new IllegalArgumentException(\"Unknown variable: \" + variable);\n            }\n\n            // Assume the expanded variables are all packages, and append a dot\n            if (!replacement.isEmpty() && matcher.end() < name.length() && name.charAt(matcher.end()) != '.') {\n                replacement += \".\";\n            }\n            matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));\n        }\n\n        matcher.appendTail(output);\n        return output.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/traffic/TinyProtocol.java",
    "content": "package com.github.games647.lagmonitor.traffic;\n\nimport com.github.games647.lagmonitor.traffic.Reflection.FieldAccessor;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.ChannelPromise;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.plugin.Plugin;\n\n/**\n * This is modified version of TinyProtocol from the ProtocolLib authors (dmulloy2 and aadnk). The not relevant things\n * are removed like player channel injection and the serverChannelHandler is modified so we can read the raw input of\n * the incoming and outgoing packets\n *\n * Original can be found here:\n * https://github.com/dmulloy2/ProtocolLib/blob/master/modules/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java\n */\npublic abstract class TinyProtocol {\n\n    // Looking up ServerConnection\n    private static final Class<Object> SERVER_CLASS = (Class<Object>) Reflection.getMinecraftClass(\"MinecraftServer\", \"MinecraftServer\");\n    private static final Class<Object> CONNECTION_CLASS = (Class<Object>) Reflection.getMinecraftClass(\"ServerConnection\", \"network.ServerConnection\");\n    private static final Reflection.MethodInvoker GET_SERVER = Reflection.getMethod(\"{obc}.CraftServer\", \"getServer\");\n    private static final FieldAccessor<Object> GET_CONNECTION = Reflection.getField(SERVER_CLASS, CONNECTION_CLASS, 0);\n\n    // Injected channel handlers\n    private final Collection<Channel> serverChannels = new ArrayList<>();\n    private ChannelInboundHandlerAdapter serverChannelHandler;\n\n    private volatile boolean closed;\n    protected final Plugin plugin;\n\n    /**\n     * Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future\n     * clients.\n     * <p>\n     * You can construct multiple instances per plugin.\n     *\n     * @param plugin - the plugin.\n     */\n    public TinyProtocol(final Plugin plugin) {\n        this.plugin = plugin;\n\n        try {\n            registerChannelHandler();\n        } catch (IllegalArgumentException illegalArgumentException) {\n            // Damn you, late bind\n            plugin.getLogger().info(\"[TinyProtocol] Delaying server channel injection due to late bind.\");\n\n            // Damn you, late bind\n            Bukkit.getScheduler().runTask(plugin, () -> {\n                registerChannelHandler();\n                plugin.getLogger().info(\"[TinyProtocol] Late bind injection successful.\");\n            });\n        }\n    }\n\n    private void createServerChannelHandler() {\n        serverChannelHandler = new ChannelInboundHandlerAdapter() {\n\n            @Override\n            public void channelRead(ChannelHandlerContext ctx, Object msg) {\n                Channel channel = (Channel) msg;\n\n                channel.pipeline().addLast(new ChannelDuplexHandler() {\n                    @Override\n                    public void channelRead(ChannelHandlerContext handlerContext, Object object) throws Exception {\n                        onChannelRead(handlerContext, object);\n                        super.channelRead(handlerContext, object);\n                    }\n\n                    @Override\n                    public void write(ChannelHandlerContext handlerContext, Object object, ChannelPromise promise)\n                            throws Exception {\n                        onChannelWrite(handlerContext, object, promise);\n                        super.write(handlerContext, object, promise);\n                    }\n                });\n\n                ctx.fireChannelRead(msg);\n            }\n        };\n    }\n\n    public abstract void onChannelRead(ChannelHandlerContext handlerContext, Object object);\n\n    public abstract void onChannelWrite(ChannelHandlerContext handlerContext, Object object, ChannelPromise promise);\n\n    @SuppressWarnings(\"unchecked\")\n    private void registerChannelHandler() {\n        Object mcServer = GET_SERVER.invoke(Bukkit.getServer());\n        Object serverConnection = GET_CONNECTION.get(mcServer);\n\n        createServerChannelHandler();\n\n        // Find the correct list, or implicitly throw an exception\n        boolean looking = true;\n        for (int i = 0; looking; i++) {\n            List<Object> list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection);\n\n            for (Object item : list) {\n                if (!(item instanceof ChannelFuture)) {\n                    break;\n                }\n\n                // Channel future that contains the server connection\n                Channel serverChannel = ((ChannelFuture) item).channel();\n\n                serverChannels.add(serverChannel);\n                serverChannel.pipeline().addFirst(serverChannelHandler);\n                looking = false;\n            }\n        }\n    }\n\n    private void unregisterChannelHandler() {\n        if (serverChannelHandler == null) {\n            return;\n        }\n\n        serverChannels.forEach(serverChannel -> {\n            // Remove channel handler\n            ChannelPipeline pipeline = serverChannel.pipeline();\n            serverChannel.eventLoop().execute(new CleanUpTask(pipeline, serverChannelHandler));\n        });\n    }\n\n    /**\n     * Cease listening for packets. This is called automatically when your plugin is disabled.\n     */\n    public final void close() {\n        if (!closed) {\n            closed = true;\n\n            unregisterChannelHandler();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/traffic/TrafficReader.java",
    "content": "package com.github.games647.lagmonitor.traffic;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufHolder;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\n\nimport java.util.concurrent.atomic.LongAdder;\n\nimport org.bukkit.plugin.Plugin;\n\npublic class TrafficReader extends TinyProtocol {\n\n    private final LongAdder incomingBytes = new LongAdder();\n    private final LongAdder outgoingBytes = new LongAdder();\n\n    public TrafficReader(Plugin plugin) {\n        super(plugin);\n    }\n\n    public LongAdder getIncomingBytes() {\n        return incomingBytes;\n    }\n\n    public LongAdder getOutgoingBytes() {\n        return outgoingBytes;\n    }\n\n    @Override\n    public void onChannelRead(ChannelHandlerContext handlerContext, Object object) {\n        onChannel(object, true);\n    }\n\n    @Override\n    public void onChannelWrite(ChannelHandlerContext handlerContext, Object object, ChannelPromise promise) {\n        onChannel(object, false);\n    }\n\n    private void onChannel(Object object, boolean incoming) {\n        ByteBuf bytes = null;\n        if (object instanceof ByteBuf) {\n            bytes = ((ByteBuf) object);\n        } else if (object instanceof ByteBufHolder) {\n            bytes = ((ByteBufHolder) object).content();\n        }\n\n        if (bytes != null) {\n            int readableBytes = bytes.readableBytes();\n            if (incoming) {\n                incomingBytes.add(readableBytes);\n            } else {\n                outgoingBytes.add(readableBytes);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/util/JavaVersion.java",
    "content": "package com.github.games647.lagmonitor.util;\n\nimport com.google.common.collect.ComparisonChain;\n\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class JavaVersion implements Comparable<JavaVersion> {\n\n    public static final JavaVersion LATEST = new JavaVersion(\"14.0.1\", 14, 0, 1, false);\n\n    private static final Pattern VERSION_PATTERN = Pattern.compile(\"((1\\\\.)?(\\\\d+))(\\\\.(\\\\d+))?(\\\\.(\\\\d+))?\");\n\n    private final String raw;\n\n    private final int major;\n    private final int minor;\n    private final int security;\n    private final boolean preRelease;\n\n    protected JavaVersion(String raw, int major, int minor, int security, boolean preRelease) {\n        this.raw = raw;\n        this.major = major;\n        this.minor = minor;\n        this.security = security;\n        this.preRelease = preRelease;\n    }\n\n    public JavaVersion(String version) {\n        raw = version;\n        preRelease = version.contains(\"-ea\") || version.contains(\"-internal\");\n\n        Matcher matcher = VERSION_PATTERN.matcher(version);\n        if (!matcher.find()) {\n            throw new IllegalStateException(\"Cannot parse Java version\");\n        }\n\n        major = Optional.ofNullable(matcher.group(3)).map(Integer::parseInt).orElse(0);\n        if (major == 8) {\n            // If you have a better solution feel free to contribute\n            // Source: https://openjdk.java.net/jeps/223\n            // Minor releases containing changes beyond security fixes are multiples of 20. Security releases based on\n            // the previous minor release are odd numbers incremented by five, or by six if necessary in order to keep\n            // the update number odd.\n            int update = Integer.parseInt(version.substring(version.indexOf('_') + 1));\n            minor = update / 20;\n            security = update % 20;\n        } else {\n            minor = Optional.ofNullable(matcher.group(5)).map(Integer::parseInt).orElse(0);\n            security = Optional.ofNullable(matcher.group(7)).map(Integer::parseInt).orElse(0);\n        }\n    }\n\n    public static JavaVersion detect() {\n        return new JavaVersion(System.getProperty(\"java.version\"));\n    }\n\n    public String getRaw() {\n        return raw;\n    }\n\n    public int getMajor() {\n        return major;\n    }\n\n    public int getMinor() {\n        return minor;\n    }\n\n    public int getSecurity() {\n        return security;\n    }\n\n    public boolean isPreRelease() {\n        return preRelease;\n    }\n\n    public boolean isOutdated() {\n        return this.compareTo(LATEST) < 0;\n    }\n\n    @Override\n    public int compareTo(JavaVersion other) {\n        return ComparisonChain.start()\n                .compare(major, other.major)\n                .compare(minor, other.minor)\n                .compare(security, other.security)\n                .compareTrueFirst(preRelease, other.preRelease)\n                .result();\n    }\n\n    @Override\n    public boolean equals(Object other) {\n        if (this == other) return true;\n        if (!(other instanceof JavaVersion)) return false;\n        JavaVersion that = (JavaVersion) other;\n        return major == that.major &&\n                minor == that.minor &&\n                security == that.security &&\n                preRelease == that.preRelease;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(raw, major, minor, security, preRelease);\n    }\n\n    @Override\n    public String toString() {\n        return this.getClass().getSimpleName() + '{' +\n                \"raw='\" + raw + '\\'' +\n                \", major=\" + major +\n                \", minor=\" + minor +\n                \", security=\" + security +\n                \", preRelease=\" + preRelease +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/util/LagUtils.java",
    "content": "package com.github.games647.lagmonitor.util;\n\nimport com.google.common.base.Enums;\n\nimport java.io.IOException;\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.stream.Stream;\n\nimport org.bukkit.Material;\n\npublic class LagUtils {\n\n    private LagUtils() {\n    }\n\n    public static int byteToMega(long bytes) {\n        return (int) (bytes / (1024 * 1024));\n    }\n\n    public static float round(double number) {\n        return round(number, 2);\n    }\n\n    public static float round(double value, int places) {\n        BigDecimal bd = new BigDecimal(value);\n        bd = bd.setScale(2, RoundingMode.HALF_UP);\n        return bd.floatValue();\n    }\n\n    /**\n     * Check if the current server version supports filled maps and MapView.setView methods.\n     * @return true if supported\n     */\n    public static boolean isFilledMapSupported() {\n        return Enums.getIfPresent(Material.class, \"FILLED_MAP\").isPresent();\n    }\n\n    public static String readableBytes(long bytes) {\n        //https://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java\n        int unit = 1024;\n        if (bytes < unit) {\n            return bytes + \" B\";\n        }\n\n        int exp = (int) (Math.log(bytes) / Math.log(unit));\n        String pre = \"kMGTPE\".charAt(exp - 1) + \"i\";\n        return String.format(\"%.2f %sB\", bytes / Math.pow(unit, exp), pre);\n    }\n\n    public static long getFolderSize(Logger logger, Path folder) {\n        try (Stream<Path> walk = Files.walk(folder, 3)) {\n            return walk\n                    .parallel()\n                    .filter(Files::isRegularFile)\n                    .mapToLong(path -> {\n                        try {\n                            return Files.size(path);\n                        } catch (IOException ioEx) {\n                            return 0;\n                        }\n                    }).sum();\n        } catch (IOException ioEx) {\n            logger.log(Level.INFO, \"Cannot walk file tree to calculate folder size\", ioEx);\n        }\n\n        return -1;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/github/games647/lagmonitor/util/RollingOverHistory.java",
    "content": "package com.github.games647.lagmonitor.util;\n\nimport java.util.Arrays;\n\npublic class RollingOverHistory {\n\n    private final float[] samples;\n    private float total;\n\n    private int currentPosition;\n    private int currentSize = 1;\n\n    public RollingOverHistory(int size, float firstValue) {\n        this.samples = new float[size];\n        reset(firstValue);\n    }\n\n    public void add(float sample) {\n        currentPosition++;\n        if (currentPosition >= samples.length) {\n            //we reached the end - go back to the beginning\n            currentPosition = 0;\n        }\n\n        if (currentSize < samples.length) {\n            //array is not full yet\n            currentSize++;\n        }\n\n        //delete the latest sample which wil be overridden\n        total -= samples[currentPosition];\n\n        total += sample;\n        samples[currentPosition] = sample;\n    }\n\n    public float getAverage() {\n        return total / currentSize;\n    }\n\n    public int getCurrentPosition() {\n        return currentPosition;\n    }\n\n    public int getCurrentSize() {\n        return currentSize;\n    }\n\n    public float getLastSample() {\n        int lastPos = currentPosition;\n        if (lastPos < 0) {\n            lastPos = samples.length - 1;\n        }\n\n        return samples[lastPos];\n    }\n\n    public float[] getSamples() {\n        return Arrays.copyOf(samples, samples.length);\n    }\n\n    public void reset(float firstVal) {\n        samples[0] = firstVal;\n        total = firstVal;\n    }\n\n    @Override\n    public String toString() {\n        return this.getClass().getSimpleName() + '{' +\n                \"samples=\" + Arrays.toString(samples) +\n                \", total=\" + total +\n                \", currentPosition=\" + currentPosition +\n                \", currentSize=\" + currentSize +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider",
    "content": "com.github.games647.lagmonitor.logging.ForwardLogService\n"
  },
  {
    "path": "src/main/resources/config.yml",
    "content": "# ${project.name} main config\n\n# If this option is enabled, this plugin will check for events which should run on the main\n# thread. If this not the cause the plugin will throw an exception to inform you. Therefore\n# you can detect thread-safety issues which could end up in ConcurrentModificationExceptions\n# or other issues.\nthread-safety-check: true\n\n# Check periodically if a server (especially a plugin) is doing block I/O operations on the main thread.\n# Operations like SQL-, HTTP-Request, File or Socket-Connections should be performed in a separate thread.\n# If this is not the case, the server will wait for the response and therefore causes lags.\n#\n# This can be useful for plugins that use thread pools, where the connection is initialized in background, but the\n# data is retrieved on the main thread (e.g. Hikari SQL database pools). The following options can't detect that.\nthread-block-detection: false\n\n# This does the same as the option above, but it proves better performance. This means it can only check for\n# Socket connections -> HTTP, SQL, but not for file operations.\nsocket-block-detection: true\n\n# This is a more efficient system as the method above (thread-block detection)\n# By setting a new security manager we can receive the operations above as events and don't have\n# to check it periodically.\n# Warning: this may override the existing security manager which could be set by your hoster\nsecurityMangerBlockingCheck: true\n\n# If you see something like: \"Server is performing a threading socket connection ...\" and then a long list with \"at ..\"\n# It's properly one of the four features above. The last part is the stacktrace.\n# This can help developers where their was running in order to find the source.\n# \n# If the developer is unreachable and it's too much for you, can deactivate it here. Then you still get the warning\n# but you don't see the stacktrace.\nhideStacktrace: false\n\n# Show the warning from above only once per plugin\noncePerPlugin: false\n\n# By hooking into the network management of Minecraft we can read how many bytes\n# the server is receiving or is sending.\ntraffic-counter: true\n\n# If you enable this, it will save some monitoring data periodically into a MySQL database\n# There you can find lag sources with a history\n# And you could create a web interface for monitoring your server\nmonitor-database: false\n\n# Database configuration\n# Recommended is the use of MariaDB (a better version of MySQL)\nhost: 127.0.0.1\nport: 3306\ndatabase: lagmonitor\nusessl: false\nusername: myUser\npassword: myPassword\ntablePrefix: 'lgm_'\n\n# Interval is in seconds\n\n# Containing the current TPS and a updated timestamp\ntps-save-interval: 300\n\n# Containing some monitoring information to analyze your log\nmonitor-save-interval: 900\n\n# Information about your server which is good to see, but might not be really useful for finding lag sources\n# For example:\n#   * native\n#   * Minecraft traffic counter\n#   * Minecraft process specific writes and reads\nnative-save-interval: 1200\n\n# A permissions independent way to allow certain commands\n#\n# Everything starts with 'allow-' and then the command name\n#\n# You can add as many commands as you want to\n# If the command doesn't exist here it won't be allowed at all\n# Everyone who has the permission for that command can then use it.\n#\n# Here an example for the native command:\n# allow-native:\n#     - PlayerName\n#     - Example\nallow-commandname:\n    - PlayerName\n    - Example\n\n\n"
  },
  {
    "path": "src/main/resources/create.sql",
    "content": "# LagMonitor table\n# Add Ids to each table, because it would be easier to refer to those entries in a lightweight way\n# Example: From a monitoring application\n# Alternatively we could also use no primary keys at all for some tables\n\nCREATE TABLE IF NOT EXISTS `{prefix}tps`\n(\n    `tps_id`  INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,\n    `tps`     FLOAT UNSIGNED NOT NULL,\n    `updated` TIMESTAMP      NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE IF NOT EXISTS `{prefix}monitor`\n(\n    `monitor_id`      INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,\n    `process_usage`   FLOAT UNSIGNED     NOT NULL,\n    `os_usage`        FLOAT UNSIGNED     NOT NULL,\n    `free_ram`        MEDIUMINT UNSIGNED NOT NULL,\n    `free_ram_pct`    FLOAT UNSIGNED     NOT NULL,\n    `os_free_ram`     MEDIUMINT UNSIGNED NOT NULL,\n    `os_free_ram_pct` FLOAT UNSIGNED     NOT NULL,\n    `load_avg`        FLOAT UNSIGNED     NOT NULL,\n    `updated`         TIMESTAMP          NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE IF NOT EXISTS `{prefix}worlds`\n(\n    `world_id`      INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,\n    `monitor_id`    INTEGER UNSIGNED   NOT NULL,\n    `world_name`    VARCHAR(255)       NOT NULL,\n    `chunks_loaded` SMALLINT UNSIGNED  NOT NULL,\n    `tile_entities` SMALLINT UNSIGNED  NOT NULL,\n    `world_size`    MEDIUMINT UNSIGNED NOT NULL,\n    `entities`      INT UNSIGNED       NOT NULL,\n    FOREIGN KEY (`monitor_id`) REFERENCES `{prefix}monitor` (`monitor_id`)\n);\n\nCREATE TABLE IF NOT EXISTS `{prefix}players`\n(\n    `world_id` INTEGER UNSIGNED,\n    `uuid`     CHAR(40)          NOT NULL,\n    `name`     VARCHAR(16)       NOT NULL,\n    `ping`     SMALLINT UNSIGNED NOT NULL,\n    PRIMARY KEY (`world_id`, `uuid`),\n    FOREIGN KEY (`world_id`) REFERENCES `{prefix}worlds` (`world_id`)\n);\n\nCREATE TABLE IF NOT EXISTS `{prefix}native`\n(\n    `native_id`      INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,\n    `mc_read`        SMALLINT UNSIGNED,\n    `mc_write`       SMALLINT UNSIGNED,\n    `free_space`     INT UNSIGNED,\n    `free_space_pct` FLOAT UNSIGNED,\n    `disk_read`      SMALLINT UNSIGNED,\n    `disk_write`     SMALLINT UNSIGNED,\n    `net_read`       SMALLINT UNSIGNED,\n    `net_write`      SMALLINT UNSIGNED,\n    `updated`        TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n"
  },
  {
    "path": "src/main/resources/default.jfc",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n     Recommended way to edit .jfc files is to use the configure command of\n     the 'jfr' tool, i.e. jfr configure, or JDK Mission Control\n     see Window -> Flight Recorder Template Manager\n-->\n\n<configuration version=\"2.0\" label=\"Continuous\" description=\"Low overhead configuration safe for continuous use in production environments, typically less than 1 % overhead.\" provider=\"Oracle\">\n\n    <event name=\"jdk.ThreadAllocationStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.ClassLoadingStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">1000 ms</setting>\n    </event>\n\n    <event name=\"jdk.ClassLoaderStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.JavaThreadStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">1000 ms</setting>\n    </event>\n\n    <event name=\"jdk.SymbolTableStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">10 s</setting>\n    </event>\n\n    <event name=\"jdk.StringTableStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">10 s</setting>\n    </event>\n\n    <event name=\"jdk.PlaceholderTableStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">10 s</setting>\n    </event>\n\n    <event name=\"jdk.LoaderConstraintsTableStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">10 s</setting>\n    </event>\n\n    <event name=\"jdk.ProtectionDomainCacheTableStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">10 s</setting>\n    </event>\n\n    <event name=\"jdk.ThreadStart\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.ThreadEnd\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.ThreadSleep\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\" control=\"locking-threshold\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.ThreadPark\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\" control=\"locking-threshold\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.JavaMonitorEnter\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\" control=\"locking-threshold\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.JavaMonitorWait\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\" control=\"locking-threshold\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.JavaMonitorInflate\">\n      <setting name=\"enabled\">false</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\" control=\"locking-threshold\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.SyncOnValueBasedClass\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.BiasedLockRevocation\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.BiasedLockSelfRevocation\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.BiasedLockClassRevocation\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.ReservedStackActivation\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.ClassLoad\">\n      <setting name=\"enabled\" control=\"class-loading\">false</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.ClassDefine\">\n      <setting name=\"enabled\" control=\"class-loading\">false</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.RedefineClasses\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.RetransformClasses\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.ClassRedefinition\">\n      <setting name=\"enabled\" control=\"class-loading\">true</setting>\n    </event>\n\n    <event name=\"jdk.ClassUnload\">\n      <setting name=\"enabled\" control=\"class-loading\">false</setting>\n    </event>\n\n    <event name=\"jdk.JVMInformation\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.InitialSystemProperty\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.ExecutionSample\">\n      <setting name=\"enabled\" control=\"method-sampling-enabled\">true</setting>\n      <setting name=\"period\" control=\"method-sampling-java-interval\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.NativeMethodSample\">\n      <setting name=\"enabled\" control=\"method-sampling-enabled\">true</setting>\n      <setting name=\"period\" control=\"method-sampling-native-interval\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.SafepointBegin\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"threshold\">10 ms</setting>\n    </event>\n\n    <event name=\"jdk.SafepointStateSynchronization\">\n      <setting name=\"enabled\">false</setting>\n      <setting name=\"threshold\">10 ms</setting>\n    </event>\n\n    <event name=\"jdk.SafepointCleanup\">\n      <setting name=\"enabled\">false</setting>\n      <setting name=\"threshold\">10 ms</setting>\n    </event>\n\n    <event name=\"jdk.SafepointCleanupTask\">\n      <setting name=\"enabled\">false</setting>\n      <setting name=\"threshold\">10 ms</setting>\n    </event>\n\n    <event name=\"jdk.SafepointEnd\">\n      <setting name=\"enabled\">false</setting>\n      <setting name=\"threshold\">10 ms</setting>\n    </event>\n\n    <event name=\"jdk.ExecuteVMOperation\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"threshold\">10 ms</setting>\n    </event>\n\n    <event name=\"jdk.Shutdown\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.ThreadDump\">\n      <setting name=\"enabled\" control=\"thread-dump-enabled\">true</setting>\n      <setting name=\"period\" control=\"thread-dump\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.IntFlag\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.UnsignedIntFlag\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.LongFlag\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.UnsignedLongFlag\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.DoubleFlag\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.BooleanFlag\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.StringFlag\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.IntFlagChanged\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.UnsignedIntFlagChanged\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.LongFlagChanged\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.UnsignedLongFlagChanged\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.DoubleFlagChanged\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.BooleanFlagChanged\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.StringFlagChanged\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.ObjectCount\">\n      <setting name=\"enabled\" control=\"gc-enabled-all\">false</setting>\n      <setting name=\"period\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.GCConfiguration\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"period\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.GCHeapConfiguration\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.YoungGenerationConfiguration\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.GCTLABConfiguration\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.GCSurvivorConfiguration\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.ObjectCountAfterGC\">\n      <setting name=\"enabled\">false</setting>\n    </event>\n\n    <event name=\"jdk.GCHeapSummary\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.PSHeapSummary\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.G1HeapSummary\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.MetaspaceSummary\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.MetaspaceGCThreshold\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.MetaspaceAllocationFailure\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.MetaspaceOOM\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.MetaspaceChunkFreeListSummary\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.GarbageCollection\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.SystemGC\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.ParallelOldGarbageCollection\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.YoungGarbageCollection\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.OldGarbageCollection\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.G1GarbageCollection\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.GCPhasePause\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.GCPhasePauseLevel1\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.GCPhasePauseLevel2\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.GCPhasePauseLevel3\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.GCPhasePauseLevel4\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.GCPhaseConcurrent\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.GCPhaseConcurrentLevel1\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.GCReferenceStatistics\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.PromotionFailed\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.EvacuationFailed\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.EvacuationInformation\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.G1MMU\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.G1EvacuationYoungStatistics\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.G1EvacuationOldStatistics\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.GCPhaseParallel\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.G1BasicIHOP\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.G1AdaptiveIHOP\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.PromoteObjectInNewPLAB\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n    </event>\n\n    <event name=\"jdk.PromoteObjectOutsidePLAB\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n    </event>\n\n    <event name=\"jdk.ConcurrentModeFailure\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.AllocationRequiringGC\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.TenuringDistribution\">\n      <setting name=\"enabled\" control=\"gc-enabled-normal\">true</setting>\n    </event>\n\n    <event name=\"jdk.G1HeapRegionInformation\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n      <setting name=\"period\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.G1HeapRegionTypeChange\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n    </event>\n\n    <event name=\"jdk.ShenandoahHeapRegionInformation\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n      <setting name=\"period\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.ShenandoahHeapRegionStateChange\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n    </event>\n\n    <event name=\"jdk.OldObjectSample\">\n      <setting name=\"enabled\" control=\"old-objects-enabled\">true</setting>\n      <setting name=\"stackTrace\" control=\"old-objects-stack-trace\">false</setting>\n      <setting name=\"cutoff\" control=\"old-objects-cutoff\">0 ns</setting>\n    </event>\n\n    <event name=\"jdk.CompilerConfiguration\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.CompilerStatistics\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n      <setting name=\"period\">1000 ms</setting>\n    </event>\n\n    <event name=\"jdk.Compilation\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n      <setting name=\"threshold\" control=\"compiler-compilation-threshold\">1000 ms</setting>\n    </event>\n\n    <event name=\"jdk.CompilerPhase\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n      <setting name=\"threshold\" control=\"compiler-phase-threshold\">60 s</setting>\n    </event>\n\n    <event name=\"jdk.CompilationFailure\">\n      <setting name=\"enabled\" control=\"compiler-enabled-failure\">false</setting>\n    </event>\n\n    <event name=\"jdk.CompilerInlining\">\n      <setting name=\"enabled\" control=\"compiler-enabled-failure\">false</setting>\n    </event>\n\n    <event name=\"jdk.CodeSweeperConfiguration\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.CodeSweeperStatistics\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n      <setting name=\"period\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.SweepCodeCache\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n      <setting name=\"threshold\" control=\"compiler-sweeper-threshold\">100 ms</setting>\n    </event>\n\n    <event name=\"jdk.CodeCacheConfiguration\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.CodeCacheStatistics\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n      <setting name=\"period\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.CodeCacheFull\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.OSInformation\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.VirtualizationInformation\">\n     <setting name=\"enabled\">true</setting>\n     <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.ContainerConfiguration\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.ContainerCPUUsage\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">30 s</setting>\n    </event>\n\n    <event name=\"jdk.ContainerCPUThrottling\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">30 s</setting>\n    </event>\n\n    <event name=\"jdk.ContainerMemoryUsage\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">30 s</setting>\n    </event>\n\n    <event name=\"jdk.ContainerIOUsage\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">30 s</setting>\n    </event>\n\n    <event name=\"jdk.CPUInformation\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.ThreadContextSwitchRate\">\n      <setting name=\"enabled\" control=\"compiler-enabled\">true</setting>\n      <setting name=\"period\">10 s</setting>\n    </event>\n\n    <event name=\"jdk.CPULoad\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">1000 ms</setting>\n    </event>\n\n    <event name=\"jdk.ThreadCPULoad\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">10 s</setting>\n    </event>\n\n    <event name=\"jdk.CPUTimeStampCounter\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.SystemProcess\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">endChunk</setting>\n    </event>\n\n    <event name=\"jdk.ProcessStart\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.NetworkUtilization\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">5 s</setting>\n    </event>\n\n    <event name=\"jdk.InitialEnvironmentVariable\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">beginChunk</setting>\n    </event>\n\n    <event name=\"jdk.PhysicalMemory\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.ObjectAllocationInNewTLAB\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.ObjectAllocationOutsideTLAB\">\n      <setting name=\"enabled\" control=\"gc-enabled-high\">false</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.ObjectAllocationSample\">\n      <setting name=\"enabled\" control=\"object-allocation-enabled\">true</setting>\n      <setting name=\"throttle\" control=\"allocation-profiling\">150/s</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.NativeLibrary\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">everyChunk</setting>\n    </event>\n\n    <event name=\"jdk.ModuleRequire\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">endChunk</setting>\n    </event>\n\n    <event name=\"jdk.ModuleExport\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">endChunk</setting>\n    </event>\n\n    <event name=\"jdk.FileForce\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\" control=\"file-threshold\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.FileRead\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\" control=\"file-threshold\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.FileWrite\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\" control=\"file-threshold\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.SocketRead\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\" control=\"socket-threshold\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.SocketWrite\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\" control=\"socket-threshold\">20 ms</setting>\n    </event>\n\n    <event name=\"jdk.Deserialization\">\n       <setting name=\"enabled\">false</setting>\n       <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.SecurityPropertyModification\">\n       <setting name=\"enabled\">false</setting>\n       <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.TLSHandshake\">\n      <setting name=\"enabled\">false</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.X509Validation\">\n       <setting name=\"enabled\">false</setting>\n       <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.X509Certificate\">\n       <setting name=\"enabled\">false</setting>\n       <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.JavaExceptionThrow\">\n      <setting name=\"enabled\" control=\"enable-exceptions\">false</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.JavaErrorThrow\">\n      <setting name=\"enabled\" control=\"enable-errors\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.ExceptionStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">1000 ms</setting>\n    </event>\n\n    <event name=\"jdk.ActiveRecording\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.ActiveSetting\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.Flush\">\n      <setting name=\"enabled\">false</setting>\n      <setting name=\"threshold\">0 ns</setting>\n    </event>\n\n    <event name=\"jdk.DataLoss\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.DumpReason\">\n      <setting name=\"enabled\">true</setting>\n    </event>\n\n    <event name=\"jdk.ZAllocationStall\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.ZPageAllocation\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">true</setting>\n      <setting name=\"threshold\">1 ms</setting>\n    </event>\n\n    <event name=\"jdk.ZRelocationSet\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.ZRelocationSetGroup\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.ZStatisticsCounter\">\n      <setting name=\"enabled\">false</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.ZStatisticsSampler\">\n      <setting name=\"enabled\">false</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.ZThreadPhase\">\n      <setting name=\"enabled\">false</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.ZUncommit\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.ZUnmap\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"threshold\">0 ms</setting>\n    </event>\n\n    <event name=\"jdk.Deoptimization\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"stackTrace\">false</setting>\n    </event>\n\n    <event name=\"jdk.HeapDump\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"threshold\">0 ns</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n    <event name=\"jdk.DirectBufferStatistics\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"period\">5 s</setting>\n    </event>\n\n    <event name=\"jdk.GCLocker\">\n      <setting name=\"enabled\">true</setting>\n      <setting name=\"threshold\">1 s</setting>\n      <setting name=\"stackTrace\">true</setting>\n    </event>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n  <!--                                                                                                        \n  Contents of the control element is not read by the JVM, it's used                                           \n  by JDK Mission Control and the 'jfr' tool to change settings that                                           \n  carry the control attribute.                                                                                \n  -->\n    <control>\n     <selection name=\"gc\" default=\"normal\" label=\"Garbage Collector\">\n        <option label=\"Off\" name=\"off\">off</option>\n        <option label=\"Normal\" name=\"normal\">normal</option>\n        <option label=\"Detailed\" name=\"detailed\">detailed</option>\n        <option label=\"High, incl. TLABs/PLABs (may cause many events)\" name=\"high\">high</option>\n        <option label=\"All, incl. Heap Statistics (may cause long GCs)\" name=\"all\">all</option>\n      </selection>\n\n      <condition name=\"gc-enabled-normal\" true=\"true\" false=\"false\">\n        <or>\n          <test name=\"gc\" operator=\"equal\" value=\"normal\"/>\n          <test name=\"gc\" operator=\"equal\" value=\"detailed\"/>\n          <test name=\"gc\" operator=\"equal\" value=\"high\"/>\n          <test name=\"gc\" operator=\"equal\" value=\"all\"/>\n        </or>\n      </condition>\n\n      <condition name=\"gc-enabled-detailed\" true=\"true\" false=\"false\">\n        <or>\n          <test name=\"gc\" operator=\"equal\" value=\"detailed\"/>\n          <test name=\"gc\" operator=\"equal\" value=\"high\"/>\n          <test name=\"gc\" operator=\"equal\" value=\"all\"/>\n        </or>\n      </condition>\n\n      <condition name=\"gc-enabled-high\" true=\"true\" false=\"false\">\n        <or>\n          <test name=\"gc\" operator=\"equal\" value=\"high\"/>\n          <test name=\"gc\" operator=\"equal\" value=\"all\"/>\n        </or>\n      </condition>\n\n      <condition name=\"gc-enabled-all\" true=\"true\" false=\"false\">\n        <test name=\"gc\" operator=\"equal\" value=\"all\"/>\n      </condition>\n\n      <selection name=\"allocation-profiling\" default=\"low\" label=\"Allocation Profiling\">\n        <option label=\"Off\" name=\"off\">0/s</option>\n        <option label=\"Low\" name=\"low\">150/s</option>\n        <option label=\"Medium\" name=\"medium\">300/s</option>\n        <option label=\"High\" name=\"high\">1000/s</option>\n        <option label=\"Maximum\" name=\"maximum\">1000000000/s</option>\n      </selection>\n\n      <condition name=\"object-allocation-enabled\" true=\"true\" false=\"false\">\n\t <not>\n          <test name=\"allocation-profiling\" operator=\"equal\" value=\"off\"/>\n        </not>\n      </condition>\n\n      <selection name=\"compiler\" default=\"normal\" label=\"Compiler\">\n        <option label=\"Off\" name=\"off\">off</option>\n        <option label=\"Normal\" name=\"normal\">normal</option>\n        <option label=\"Detailed\" name=\"detailed\">detailed</option>\n        <option label=\"All\" name=\"all\">all</option>\n      </selection>\n\n      <condition name=\"compiler-enabled\" true=\"false\" false=\"true\">\n        <test name=\"compiler\" operator=\"equal\" value=\"off\"/>\n      </condition>\n\n      <condition name=\"compiler-enabled-failure\" true=\"true\" false=\"false\">\n        <or>\n          <test name=\"compiler\" operator=\"equal\" value=\"detailed\"/>\n          <test name=\"compiler\" operator=\"equal\" value=\"all\"/>\n        </or>\n      </condition>\n\n      <condition name=\"compiler-sweeper-threshold\" true=\"0 ms\" false=\"100 ms\">\n        <test name=\"compiler\" operator=\"equal\" value=\"all\"/>\n      </condition>\n\n      <condition name=\"compiler-compilation-threshold\" true=\"1000 ms\">\n        <test name=\"compiler\" operator=\"equal\" value=\"normal\"/>\n      </condition>\n\n      <condition name=\"compiler-compilation-threshold\" true=\"100 ms\">\n        <test name=\"compiler\" operator=\"equal\" value=\"detailed\"/>\n      </condition>\n\n      <condition name=\"compiler-compilation-threshold\" true=\"0 ms\">\n        <test name=\"compiler\" operator=\"equal\" value=\"all\"/>\n      </condition>\n\n      <condition name=\"compiler-phase-threshold\" true=\"60 s\">\n        <test name=\"compiler\" operator=\"equal\" value=\"normal\"/>\n      </condition>\n\n      <condition name=\"compiler-phase-threshold\" true=\"10 s\">\n        <test name=\"compiler\" operator=\"equal\" value=\"detailed\"/>\n      </condition>\n\n      <condition name=\"compiler-phase-threshold\" true=\"0 s\">\n        <test name=\"compiler\" operator=\"equal\" value=\"all\"/>\n      </condition>\n\n      <selection name=\"method-profiling\" default=\"normal\" label=\"Method Profiling\">\n        <option label=\"Off\" name=\"off\">off</option>\n        <option label=\"Normal\" name=\"normal\">normal</option>\n        <option label=\"High\" name=\"high\">high</option>\n        <option label=\"Maximum (High Overhead)\" name=\"max\">max</option>\n      </selection>\n\n      <condition name=\"method-sampling-java-interval\" true=\"999 d\">\n        <test name=\"method-profiling\" operator=\"equal\" value=\"off\"/>\n      </condition>\n\n      <condition name=\"method-sampling-java-interval\" true=\"20 ms\">\n        <test name=\"method-profiling\" operator=\"equal\" value=\"normal\"/>\n      </condition>\n\n      <condition name=\"method-sampling-java-interval\" true=\"10 ms\">\n        <test name=\"method-profiling\" operator=\"equal\" value=\"high\"/>\n      </condition>\n\n      <condition name=\"method-sampling-java-interval\" true=\"1 ms\">\n        <test name=\"method-profiling\" operator=\"equal\" value=\"max\"/>\n      </condition>\n\n      <condition name=\"method-sampling-native-interval\" true=\"999 d\">\n        <test name=\"method-profiling\" operator=\"equal\" value=\"off\"/>\n      </condition>\n\n      <condition name=\"method-sampling-native-interval\" true=\"20 ms\">\n        <or>\n          <test name=\"method-profiling\" operator=\"equal\" value=\"normal\"/>\n          <test name=\"method-profiling\" operator=\"equal\" value=\"high\"/>\n          <test name=\"method-profiling\" operator=\"equal\" value=\"max\"/>\n        </or>\n      </condition>\n\n      <condition name=\"method-sampling-enabled\" true=\"false\" false=\"true\">\n        <test name=\"method-profiling\" operator=\"equal\" value=\"off\"/>\n      </condition>\n\n      <selection name=\"thread-dump\" default=\"once\" label=\"Thread Dump\">\n        <option label=\"Off\" name=\"off\">999 d</option>\n        <option label=\"At least Once\" name=\"once\">everyChunk</option>\n        <option label=\"Every 60 s\" name=\"60s\">60 s</option>\n        <option label=\"Every 10 s\" name=\"10s\">10 s</option>\n        <option label=\"Every 1 s\" name=\"1s\">1 s</option>\n      </selection>\n\n      <condition name=\"thread-dump-enabled\" true=\"false\" false=\"true\">\n        <test name=\"thread-dump\" operator=\"equal\" value=\"999 d\"/>\n      </condition>\n\n      <selection name=\"exceptions\" default=\"errors\" label=\"Exceptions\">\n        <option label=\"Off\" name=\"off\">off</option>\n        <option label=\"Errors Only\" name=\"errors\">errors</option>\n        <option label=\"All Exceptions, including Errors\" name=\"all\">all</option>\n      </selection>\n\n      <condition name=\"enable-errors\" true=\"true\" false=\"false\">\n        <or>\n          <test name=\"exceptions\" operator=\"equal\" value=\"errors\"/>\n          <test name=\"exceptions\" operator=\"equal\" value=\"all\"/>\n        </or>\n      </condition>\n\n      <condition name=\"enable-exceptions\" true=\"true\" false=\"false\">\n        <test name=\"exceptions\" operator=\"equal\" value=\"all\"/>\n      </condition>\n\n      <selection name=\"memory-leaks\" default=\"types\" label=\"Memory Leak Detection\">\n        <option label=\"Off\" name=\"off\">off</option>\n        <option label=\"Object Types\" name=\"types\">types</option>\n        <option label=\"Object Types + Allocation Stack Traces\" name=\"stack-traces\">stack-traces</option>\n        <option label=\"Object Types + Allocation Stack Traces + Path to GC Root\" name=\"gc-roots\">gc-roots</option>\n      </selection>\n\n      <condition name=\"old-objects-enabled\" true=\"false\" false=\"true\">\n        <test name=\"memory-leaks\" operator=\"equal\" value=\"off\"/>\n      </condition>\n\n      <condition name=\"old-objects-stack-trace\" true=\"true\" false=\"false\">\n        <or>\n          <test name=\"memory-leaks\" operator=\"equal\" value=\"stack-traces\"/>\n          <test name=\"memory-leaks\" operator=\"equal\" value=\"gc-roots\"/>\n        </or>\n      </condition>\n\n      <condition name=\"old-objects-cutoff\" true=\"1 h\" false=\"0 ns\">\n        <test name=\"memory-leaks\" operator=\"equal\" value=\"gc-roots\"/>\n      </condition>\n\n      <text name=\"locking-threshold\" label=\"Locking Threshold\" contentType=\"timespan\" minimum=\"0 s\">20 ms</text>\n\n      <text name=\"file-threshold\" label=\"File I/O Threshold\" contentType=\"timespan\" minimum=\"0 s\">20 ms</text>\n\n      <text name=\"socket-threshold\" label=\"Socket I/O Threshold\" contentType=\"timespan\" minimum=\"0 s\">20 ms</text>\n\n      <flag name=\"class-loading\" label=\"Class Loading\">false</flag>\n\n    </control>\n\n</configuration>\n"
  },
  {
    "path": "src/main/resources/plugin.yml",
    "content": "# project data for Bukkit in order to register our plugin with all it components\n# ${-} are variables from Maven (pom.xml) which will be replaced after the build\nname: ${project.name}\nversion: ${project.version}-${git.commit.id.abbrev}\nmain: ${project.groupId}.${project.artifactId}.${project.name}\n\n# meta data for plugin managers\nauthors: [games647, 'https://github.com/games647/LagMonitor/graphs/contributors']\ndescription: |\n    ${project.description}\nwebsite: ${project.url}\ndev-url: ${project.url}\n\n# This plugin don't have to be transformed for compatibility with Minecraft >= 1.13\napi-version: 1.16\n\nlibraries:\n    - net.java.dev.jna:jna:5.12.1\n\ncommands:\n    lagmonitor:\n        description: 'Gets displays the help page of all lagmonitor commands'\n        permission: ${project.artifactId}.command.help\n    ping:\n        description: 'Gets the ping of the selected player'\n        usage: '/<command> [player]'\n        permission: ${project.artifactId}.command.ping\n    stacktrace:\n        description: 'Gets the execution stacktrace of selected thread'\n        usage: '/<command> [threadName]'\n        permission: ${project.artifactId}.command.stacktrace\n    thread:\n        description: 'Outputs all running threads with their current state'\n        usage: '/<command> [dump]'\n        aliases: [threads]\n        permission: ${project.artifactId}.command.thread\n    tpshistory:\n        description: 'Outputs the current tps'\n        aliases: [tps, lag]\n        permission: ${project.artifactId}.command.tps\n    mbean:\n        description: 'Outputs mbeans attributes (java environment information)'\n        aliases: [bean]\n        usage: '/<command> [beanName] [attribute]'\n        permission: ${project.artifactId}.command.mbean\n    system:\n        description: 'Gives you some general information (Minecraft server related)'\n        permission: ${project.artifactId}.command.system\n    timing:\n        description: 'Outputs your server timings ingame'\n        permission: ${project.artifactId}.command.timing\n    monitor:\n        description: 'Monitors the CPU usage of methods'\n        permission: ${project.artifactId}.command.monitor\n        usage: '/<command> [start/stop]'\n        aliases: [profile, profiler, prof]\n    graph:\n        description: 'Gives you visual graph about your server'\n        usage: '/<command> [heap/cpu/threads/classes]'\n        permission: ${project.artifactId}.command.graph\n    environment:\n        description: 'Gives you some general information (OS related)'\n        permission: ${project.artifactId}.command.environment\n        aliases: [env]\n    native:\n        description: 'Gives you information about your Hardware'\n        permission: ${project.artifactId}.command.native\n    vm:\n        description: 'Gives you information about your Hardware'\n        aliases: [virtualmachine, machine, virtual]\n        permission: ${project.artifactId}.command.vm\n    network:\n        description: 'Gives you information about your Network configuration'\n        aliases: [net]\n        permission: ${project.artifactId}.command.network\n    tasks:\n        description: 'Information about running and pending tasks'\n        aliases: [task]\n        permission: ${project.artifactId}.command.tasks\n    heap:\n        description: 'Heap dump about your current memory'\n        aliases: [ram, memory]\n        usage: /<command> [dump]\n        permission: ${project.artifactId}.command.heap\n    lagpage:\n        description: 'Pages command for the current pagination session'\n        usage: '/<command> <next/prev/number>'\n    jfr:\n        description: |\n            'Manages the Java Flight Recordings of the native Java VM. It gives you much more detailed information\n            including network communications, file read/write times, detailed heap and thread data, ...'\n        aliases: [flightrecoder]\n        usage: '/<command> <start/stop/dump>'\n        permission: ${project.artifactId}.command.jfr\n\npermissions:\n    ${project.artifactId}.*:\n        description: Gives access to all ${project.name} Features\n        children:\n            ${project.artifactId}.commands.*: true\n\n    ${project.artifactId}.commands.*:\n        description: Gives access to all ${project.name} commands\n        children:\n            ${project.artifactId}.command.ping: true\n            ${project.artifactId}.command.ping.other: true\n            ${project.artifactId}.command.stacktrace: true\n            ${project.artifactId}.command.thread: true\n            ${project.artifactId}.command.tps: true\n            ${project.artifactId}.command.mbean: true\n            ${project.artifactId}.command.system: true\n            ${project.artifactId}.command.timing: true\n            ${project.artifactId}.command.monitor: true\n            ${project.artifactId}.command.graph: true\n            ${project.artifactId}.command.native: true\n            ${project.artifactId}.command.vm: true\n            ${project.artifactId}.command.network: true\n            ${project.artifactId}.command.tasks: true\n            ${project.artifactId}.command.jfr: true\n\n    ${project.artifactId}.command.ping.other:\n        description: 'Get the ping from other players'\n        children:\n            ${project.artifactId}.command.ping: true\n"
  },
  {
    "path": "src/test/java/com/github/games647/lagmonitor/LagMonitorTest.java",
    "content": "package com.github.games647.lagmonitor;\n\nimport java.time.Duration;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class LagMonitorTest {\n\n    @Test\n    public void testEmptyDuration() {\n        assertEquals(\"'0' days '0' hours '0' minutes '0' seconds'\", LagMonitor.formatDuration(Duration.ZERO));\n    }\n\n    @Test\n    public void testOverYearDuration() {\n        String expected = \"'362' days '0' hours '0' minutes '0' seconds'\";\n        assertEquals(expected, LagMonitor.formatDuration(Duration.ofDays(362)));\n    }\n\n    @Test\n    public void testValidSecondDuration() {\n        String expected = \"'0' days '0' hours '0' minutes '1' seconds'\";\n        assertEquals(expected, LagMonitor.formatDuration(Duration.ofSeconds(1)));\n    }\n\n    @Test\n    public void testOverSecondDuration() {\n        String expected = \"'0' days '0' hours '1' minutes '15' seconds'\";\n        assertEquals(expected, LagMonitor.formatDuration(Duration.ofSeconds(75)));\n    }\n\n    @Test\n    public void testFormattingCombined() {\n        String expected = \"'0' days '0' hours '13' minutes '15' seconds'\";\n        assertEquals(expected, LagMonitor.formatDuration(Duration.ofSeconds(75).plusMinutes(12)));\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/github/games647/lagmonitor/RollingOverHistoryTest.java",
    "content": "package com.github.games647.lagmonitor;\n\nimport com.github.games647.lagmonitor.util.RollingOverHistory;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class RollingOverHistoryTest {\n\n    @Test\n    public void testGetAverage() {\n        RollingOverHistory history = new RollingOverHistory(4, 1);\n\n        assertEquals(1.0F, history.getAverage());\n        history.add(3);\n        assertEquals(2.0F, history.getAverage());\n        history.add(2);\n        assertEquals(2.0F, history.getAverage());\n        history.add(3);\n        assertEquals(2.25F, history.getAverage());\n    }\n\n    @Test\n    public void testGetCurrentPosition() {\n        RollingOverHistory history = new RollingOverHistory(2, 1);\n\n        assertEquals(0, history.getCurrentPosition());\n        history.add(2);\n\n        assertEquals(1, history.getCurrentPosition());\n        history.add(2);\n        //reached the max size\n        assertEquals(0, history.getCurrentPosition());\n    }\n\n    @Test\n    public void testGetLastSample() {\n        RollingOverHistory history = new RollingOverHistory(3, 1);\n\n        assertEquals(1.0, history.getLastSample());\n        history.add(2);\n        assertEquals(2.0, history.getLastSample());\n        history.add(3);\n        assertEquals(3.0, history.getLastSample());\n        history.add(2);\n        assertEquals(2.0, history.getLastSample());\n    }\n\n    @Test\n    public void testGetSamples() {\n        RollingOverHistory history = new RollingOverHistory(1, 1);\n\n        history.add(2);\n        assertArrayEquals(new float[]{2.0F}, history.getSamples());\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/github/games647/lagmonitor/listener/BlockingConnectionSelectorTest.java",
    "content": "package com.github.games647.lagmonitor.listener;\n\nimport com.github.games647.lagmonitor.threading.BlockingActionManager;\n\nimport java.net.URI;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport static org.mockito.ArgumentMatchers.anyString;\n\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\n@ExtendWith(MockitoExtension.class)\npublic class BlockingConnectionSelectorTest {\n\n    @Mock\n    private BlockingActionManager actionManager;\n    private BlockingConnectionSelector selector;\n\n    @BeforeEach\n    public void setUp() throws Exception {\n        this.selector = new BlockingConnectionSelector(actionManager);\n    }\n\n    @Test\n    public void testHttp() throws Exception {\n        selector.select(URI.create(\"https://spigotmc.org\"));\n        verify(actionManager, times(1)).checkBlockingAction(anyString());\n    }\n\n    @Test\n    public void testDuplicateHttp() throws Exception {\n        //http creates to proxy selector events one for http address and one for the socket one\n        //the second one should be ignored\n        selector.select(URI.create(\"socket://api.mojang.com:443\"));\n        verify(actionManager, times(0)).checkBlockingAction(anyString());\n    }\n\n    @Test\n    public void testBlockingSocket() throws Exception {\n        selector.select(URI.create(\"socket://api.mojang.com:50\"));\n        verify(actionManager, times(1)).checkBlockingAction(anyString());\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/github/games647/lagmonitor/util/JavaVersionTest.java",
    "content": "package com.github.games647.lagmonitor.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class JavaVersionTest {\n\n    @Test\n    public void detectDeveloperVersion() {\n        assertNotNull(JavaVersion.detect());\n    }\n\n    @Test\n    public void parseJava8() {\n        JavaVersion version = new JavaVersion(\"1.8.0_161\");\n        assertEquals(8, version.getMajor());\n        assertEquals(8, version.getMinor());\n        assertEquals(1, version.getSecurity());\n        assertTrue(version.isOutdated());\n    }\n\n    @Test\n    public void parseJava9() {\n        JavaVersion version = new JavaVersion(\"9.0.4\");\n        assertEquals(9, version.getMajor());\n        assertEquals(0, version.getMinor());\n        assertEquals(4, version.getSecurity());\n        assertTrue(version.isOutdated());\n    }\n\n    @Test\n    public void parseJava9EarlyAccess() {\n        JavaVersion version = new JavaVersion(\"9-ea\");\n        assertEquals(9, version.getMajor());\n        assertEquals(0, version.getMinor());\n        assertEquals(0, version.getSecurity());\n        assertTrue(version.isPreRelease());\n        assertTrue(version.isOutdated());\n    }\n\n    @Test\n    public void parseJava9WithVendorSuffix() {\n        JavaVersion version = new JavaVersion(\"9-Ubuntu\");\n        assertEquals(9, version.getMajor());\n        assertEquals(0, version.getMinor());\n        assertEquals(0, version.getSecurity());\n        assertTrue(version.isOutdated());\n    }\n\n    @Test\n    public void parseJava14() {\n        JavaVersion version = new JavaVersion(\"14.0.1\");\n        assertEquals(14, version.getMajor());\n        assertEquals(0, version.getMinor());\n        assertEquals(1, version.getSecurity());\n        assertFalse(version.isOutdated());\n    }\n\n    @Test\n    public void parseJava10Internal() {\n        JavaVersion version = new JavaVersion(\"10-internal\");\n        assertEquals(10, version.getMajor());\n        assertEquals(0, version.getMinor());\n        assertEquals(0, version.getSecurity());\n        assertTrue(version.isPreRelease());\n        assertTrue(version.isOutdated());\n    }\n\n    @Test\n    public void comparePreRelease() {\n        JavaVersion lower = new JavaVersion(\"10-internal\");\n        JavaVersion higher = new JavaVersion(\"10\");\n        assertEquals(-1, lower.compareTo(higher));\n    }\n\n    @Test\n    public void compareMinor() {\n        JavaVersion lower = new JavaVersion(\"9.0.3\");\n        JavaVersion higher = new JavaVersion(\"9.0.4\");\n        assertEquals(1, higher.compareTo(lower));\n    }\n\n    @Test\n    public void compareMajor() {\n        JavaVersion lower = new JavaVersion(\"1.8.0_161\");\n        JavaVersion higher = new JavaVersion(\"10\");\n        assertEquals(1, higher.compareTo(lower));\n    }\n\n    @Test\n    public void compareEqual() {\n        JavaVersion lower = new JavaVersion(\"10-Ubuntu\");\n        JavaVersion higher = new JavaVersion(\"10\");\n        assertEquals(0, lower.compareTo(higher));\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/github/games647/lagmonitor/util/LagUtilsTest.java",
    "content": "package com.github.games647.lagmonitor.util;\n\nimport java.util.Locale;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class LagUtilsTest {\n\n    @Test\n    public void byteToMega() {\n        assertEquals(1, LagUtils.byteToMega(1024 * 1024));\n        assertEquals(0, LagUtils.byteToMega(1000 * 1000));\n    }\n\n    @Test\n    public void readableBytes() {\n        //make tests that have a constant floating point separator (, vs .)\n        Locale.setDefault(Locale.ENGLISH);\n\n        assertEquals(\"1.00 kiB\", LagUtils.readableBytes(1024));\n        assertEquals(\"64 B\", LagUtils.readableBytes(64));\n        assertEquals(\"1.00 MiB\", LagUtils.readableBytes(1024 * 1024 + 12));\n    }\n}\n"
  }
]