diff --git a/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/misc/BindingExtensions.kt b/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/misc/BindingExtensions.kt
new file mode 100644
index 000000000..5868c908d
--- /dev/null
+++ b/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/misc/BindingExtensions.kt
@@ -0,0 +1,148 @@
+/*
+ *   Anvil - AnvilPowered
+ *   Copyright (C) 2020-2021
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU Lesser General Public License as published by
+ *     the Free Software Foundation, either version 3 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU Lesser General Public License for more details.
+ *
+ *     You should have received a copy of the GNU Lesser General Public License
+ *     along with this program.  If not, see .
+ */
+
+package org.anvilpowered.anvil.api.misc
+
+import com.google.common.reflect.TypeToken
+import com.google.inject.Key
+import com.google.inject.Provider
+import com.google.inject.TypeLiteral
+import org.anvilpowered.anvil.api.Anvil
+import org.anvilpowered.anvil.api.datastore.DBComponent
+
+@Suppress("unchecked", "UnstableApiUsage")
+interface BindingExtensions {
+
+  /**
+   * Full binding method for a component
+   *
+   * A typical example of usage of this method:
+   *
+   * be.bind(
+   *
+   *   new TypeToken>(getClass()) {
+   * },
+   *
+   *   new TypeToken>(getClass()) {
+   * },
+   *
+   *   new TypeToken, Datastore>>(getClass()) {
+   * },
+   *
+   *   new TypeToken>(getClass()) { // final implementation
+   * },
+   *
+   *   Names.named("mongodb")
+   *
+   * );
+   */
+  fun , From2 : DBComponent<*, *>, From3 : From1, Target : From1> bind(
+    from1: TypeToken,
+    from2: TypeToken,
+    from3: TypeToken,
+    target: TypeToken,
+    componentAnnotation: Annotation,
+  )
+
+  /**
+   * Binding method for a component
+   *
+   *
+   * A typical example of usage of this method:
+   *
+   * be.bind(
+   *
+   *   new TypeToken>(getClass()) {
+   * },
+   *
+   *   new TypeToken>(getClass()) {
+   * },
+   *
+   *   new TypeToken(getClass()) { // final implementation
+   * },
+   *   Names.named("mongodb")
+   *
+   * );
+   */
+  fun , From2 : From1, Target : From1> bind(
+    from1: TypeToken,
+    from2: TypeToken,
+    target: TypeToken,
+    componentAnnotation: Annotation,
+  )
+
+  fun  bind(
+    from: TypeToken,
+    target: TypeToken,
+    annotation: Annotation,
+  )
+
+  fun  bind(
+    from: TypeToken,
+    target: TypeToken,
+    annotation: Class,
+  )
+
+  fun  bind(
+    from: TypeToken,
+    target: TypeToken,
+  )
+
+  /**
+   * Binds the mongodb [DataStoreContext]
+   *
+   * Using this method is the same as invoking:
+   *
+   * bind(new TypeLiteral>() {
+   * }).to(MongoContext.class);
+   */
+  fun withMongoDB()
+
+  /**
+   * Binds the xodus [DataStoreContext]
+   *
+   * Using this method is the same as invoking:
+   *
+   * binder.bind(new TypeLiteral>() {
+   * }).to(XodusContext.class);
+   *
+   */
+  fun withXodus()
+
+  companion object {
+    fun  getTypeLiteral(typeToken: TypeToken): TypeLiteral {
+      return TypeLiteral.get(typeToken.type) as TypeLiteral
+    }
+
+    fun  getKey(typeToken: TypeToken): Key? {
+      return Key.get(getTypeLiteral(typeToken))
+    }
+
+    fun  asInternalProvider(clazz: Class): Provider {
+      return Provider { Anvil.getEnvironment().injector.getInstance(clazz) }
+    }
+
+    fun  asInternalProvider(typeLiteral: TypeLiteral): Provider {
+      return Anvil.getEnvironment().injector.getProvider(Key.get(typeLiteral))
+    }
+
+    fun  asInternalProvider(typeToken: TypeToken): Provider {
+      return Anvil.getEnvironment().injector.getProvider(getKey(typeToken))
+    }
+  }
+}
diff --git a/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/plugin/PluginInfo.kt b/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/plugin/PluginInfo.kt
index 0bf49e49d..72a8c853b 100644
--- a/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/plugin/PluginInfo.kt
+++ b/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/plugin/PluginInfo.kt
@@ -38,4 +38,6 @@ interface PluginInfo : Named {
   val organizationName: String
 
   val buildDate: String
+
+  val metricIds: Map
 }
diff --git a/anvil-bungee/build.gradle b/anvil-bungee/build.gradle
index a771bac64..25e2c46a9 100644
--- a/anvil-bungee/build.gradle
+++ b/anvil-bungee/build.gradle
@@ -14,6 +14,7 @@ dependencies {
     }
 
     implementation bungee
+    implementation bstats
     implementation configurate_hocon
     implementation javasisst
     implementation(kotlin_reflect + ":" + kotlin_version)
@@ -50,6 +51,7 @@ shadowJar {
         include dependency(apache_commons)
         include dependency(aopalliance)
         include dependency(bson)
+        include dependency(bstats)
         include dependency(configurate_core)
         include dependency(configurate_hocon)
         include dependency(guice)
diff --git a/anvil-bungee/src/main/kotlin/org/anvilpowered/anvil/bungee/metric/BungeeMetricService.kt b/anvil-bungee/src/main/kotlin/org/anvilpowered/anvil/bungee/metric/BungeeMetricService.kt
new file mode 100644
index 000000000..aec035916
--- /dev/null
+++ b/anvil-bungee/src/main/kotlin/org/anvilpowered/anvil/bungee/metric/BungeeMetricService.kt
@@ -0,0 +1,265 @@
+/*
+ *   Anvil - AnvilPowered
+ *   Copyright (C) 2020-2021
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU Lesser General Public License as published by
+ *     the Free Software Foundation, either version 3 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU Lesser General Public License for more details.
+ *
+ *     You should have received a copy of the GNU Lesser General Public License
+ *     along with this program.  If not, see .
+ */
+
+package org.anvilpowered.anvil.bungee.metric
+
+import com.google.gson.JsonArray
+import com.google.gson.JsonObject
+import com.google.inject.Inject
+import java.io.BufferedReader
+import java.io.BufferedWriter
+import java.io.ByteArrayOutputStream
+import java.io.DataOutputStream
+import java.io.File
+import java.io.FileReader
+import java.io.FileWriter
+import java.io.IOException
+import java.io.InputStreamReader
+import java.lang.reflect.InvocationTargetException
+import java.net.URL
+import java.nio.charset.StandardCharsets
+import java.util.UUID
+import java.util.concurrent.TimeUnit
+import java.util.zip.GZIPOutputStream
+import javax.net.ssl.HttpsURLConnection
+import net.md_5.bungee.api.ProxyServer
+import net.md_5.bungee.api.plugin.Plugin
+import net.md_5.bungee.config.Configuration
+import net.md_5.bungee.config.ConfigurationProvider
+import net.md_5.bungee.config.YamlConfiguration
+import org.anvilpowered.anvil.api.Environment
+import org.anvilpowered.anvil.common.metric.MetricService
+import org.slf4j.Logger
+
+class BungeeMetricService @Inject constructor(
+  private val logger: Logger
+) : MetricService {
+
+  private lateinit var environment: Environment
+  private var serviceId = 0
+  private var enabled = false
+  private var serverUUID: String? = null
+  private var logFailedRequests = false
+  private var logSentData = false
+  private var logResponseStatusText = false
+  private val knownMetricsInstances: MutableList = ArrayList()
+
+  override fun initialize(env: Environment) {
+    val bungeeId: Int? = env.pluginInfo.metricIds["bungeecord"]
+    requireNotNull(bungeeId) { "Could not find a valid Metrics Id for BungeeCord. Please check your PluginInfo" }
+    initialize(env, bungeeId)
+  }
+
+  private fun initialize(environment: Environment, serviceId: Int) {
+    this.environment = environment
+    this.serviceId = serviceId
+    try {
+      loadConfig()
+    } catch (e: IOException) {
+      logger.error("Failed to load bStats config!", e)
+      return
+    }
+
+    if (!enabled) {
+      return
+    }
+
+    val usedMetricsClass = getFirstBStatsClass() ?: return
+    if (usedMetricsClass == javaClass) {
+      linkMetrics(this)
+      startSubmitting()
+    } else {
+      val logMsg = "Failed to link to first metrics class ${usedMetricsClass.name}"
+      try {
+        usedMetricsClass.getMethod("linkMetrics", Any::class.java).invoke(null, this)
+      } catch (e: NoSuchMethodException) {
+        if (logFailedRequests) {
+          logger.error(logMsg, e)
+        }
+      } catch (e: IllegalAccessException) {
+        if (logFailedRequests) {
+          logger.error(logMsg, e)
+        }
+      } catch (e: InvocationTargetException) {
+        if (logFailedRequests) {
+          logger.error(logMsg, e)
+        }
+      }
+    }
+  }
+
+  private fun linkMetrics(metrics: Any) = knownMetricsInstances.add(metrics)
+
+  private fun startSubmitting() {
+    val initialDelay = (1000 * 60 * (3 + Math.random() * 3)).toLong()
+    val secondDelay = (1000 * 60 * (Math.random() * 30)).toLong()
+    val plugin = environment.plugin as Plugin
+    ProxyServer.getInstance().scheduler.schedule(plugin, { submitData() }, initialDelay, TimeUnit.MILLISECONDS)
+    ProxyServer.getInstance().scheduler.schedule(
+      plugin, { submitData() }, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS
+    )
+  }
+
+  private fun getServerData(): JsonObject {
+    val proxyInstance = ProxyServer.getInstance()
+    val data = JsonObject()
+    data.addProperty("serverUUID", serverUUID)
+    data.addProperty("playerAmount", proxyInstance.onlineCount)
+    data.addProperty("managedServers", proxyInstance.servers.size)
+    data.addProperty("onlineMode", if (proxyInstance.config.isOnlineMode) 1 else 0)
+    data.addProperty("bungeecordVersion", proxyInstance.version)
+    data.addProperty("javaVersion", System.getProperty("java.version"))
+    data.addProperty("osName", System.getProperty("os.name"))
+    data.addProperty("osArch", System.getProperty("os.arch"))
+    data.addProperty("osVersion", System.getProperty("os.version"))
+    data.addProperty("coreCount", Runtime.getRuntime().availableProcessors())
+    return data
+  }
+
+  private fun submitData() {
+    val data: JsonObject = getServerData()
+    val pluginData = JsonArray()
+    for (metrics in knownMetricsInstances) {
+      try {
+        val plugin = metrics.javaClass.getMethod("getPluginData").invoke(metrics)
+        if (plugin is JsonObject) {
+          pluginData.add(plugin)
+        }
+      } catch (ignored: Exception) {
+      }
+    }
+    data.add("plugins", pluginData)
+    try {
+      sendData(data)
+    } catch (e: Exception) {
+      if (logFailedRequests) {
+        logger.error("Could not submit plugin stats!", e)
+      }
+    }
+  }
+
+  @Throws(IOException::class)
+  private fun loadConfig() {
+    val bStatsFolder = File("plugins/bStats")
+    check(bStatsFolder.mkdirs()) { "Could not create the config directory for bStats!" }
+    val configFile = File(bStatsFolder, "config.yml")
+    check(configFile.mkdirs()) { "Could not create the config file for bStats!" }
+
+    writeFile(
+      configFile,
+      "#bStats collects some data for plugin authors like how many servers are using their plugins.",
+      "#To honor their work, you should not disable it.",
+      "#This has nearly no effect on the server performance!",
+      "#Check out https://bStats.org/ to learn more :)",
+      "enabled: true",
+      "serverUuid: \"" + UUID.randomUUID() + "\"",
+      "logFailedRequests: false",
+      "logSentData: false",
+      "logResponseStatusText: false"
+    )
+
+    val configuration: Configuration = ConfigurationProvider.getProvider(YamlConfiguration::class.java).load(configFile)
+
+    enabled = configuration.getBoolean("enabled", true)
+    serverUUID = configuration.getString("serverUuid")
+    logFailedRequests = configuration.getBoolean("logFailedRequests", false)
+    logSentData = configuration.getBoolean("logSentData", false)
+    logResponseStatusText = configuration.getBoolean("logResponseStatusText", false)
+  }
+
+  private fun getFirstBStatsClass(): Class<*>? {
+    val bStatsFolder = File("plugins/bStats")
+    bStatsFolder.mkdirs()
+    check(bStatsFolder.mkdirs()) { "Could not create the bStats config folder!" }
+    val tempFile = File(bStatsFolder, "temp.txt")
+    return try {
+      val className = readFile(tempFile)
+      if (className != null) {
+        try {
+          return Class.forName(className)
+        } catch (ignored: ClassNotFoundException) {
+        }
+      }
+      writeFile(tempFile, javaClass.name)
+      javaClass
+    } catch (e: IOException) {
+      if (logFailedRequests) {
+        logger.error("Failed to get first bStats class!", e)
+      }
+      null
+    }
+  }
+
+  @Throws(IOException::class)
+  private fun readFile(file: File): String? {
+    return if (!file.exists()) {
+      null
+    } else {
+      BufferedReader(FileReader(file)).use { it.readLine() }
+    }
+  }
+
+  @Throws(IOException::class)
+  private fun writeFile(file: File, vararg lines: String) {
+    BufferedWriter(FileWriter(file)).use {
+      for (line in lines) {
+        it.write(line)
+        it.newLine()
+      }
+    }
+  }
+
+  private fun sendData(data: JsonObject?) {
+    requireNotNull(data) { "Data cannot be null" }
+    if (logSentData) {
+      logger.info("Sending data to bStats: $data")
+    }
+    val connection: HttpsURLConnection = URL("https://bStats.org/submitData/bungeecord").openConnection() as HttpsURLConnection
+    val compressedData = compress(data.toString())
+
+    connection.requestMethod = "POST"
+    connection.addRequestProperty("Accept", "application/json")
+    connection.addRequestProperty("Connection", "close")
+    connection.addRequestProperty("Content-Encoding", "gzip")
+    connection.addRequestProperty("Content-Length", compressedData!!.size.toString())
+    connection.setRequestProperty("Content-Type", "application/json")
+    connection.setRequestProperty("User-Agent", "MC-Server/1")
+
+    connection.doOutput = true
+    DataOutputStream(connection.outputStream).use { outputStream -> outputStream.write(compressedData) }
+    val builder = StringBuilder()
+    BufferedReader(InputStreamReader(connection.inputStream)).use { bufferedReader ->
+      var line: String?
+      while (bufferedReader.readLine().also { line = it } != null) {
+        builder.append(line)
+      }
+    }
+    if (logResponseStatusText) {
+      logger.info("Sent data to bStats and received response: $builder")
+    }
+  }
+
+  private fun compress(str: String?): ByteArray? {
+    if (str == null) {
+      return null
+    }
+    val outputStream = ByteArrayOutputStream()
+    GZIPOutputStream(outputStream).use { gzip -> gzip.write(str.toByteArray(StandardCharsets.UTF_8)) }
+    return outputStream.toByteArray()
+  }
+}
diff --git a/anvil-common/build.gradle b/anvil-common/build.gradle
index 2d2900cf5..93ec9ff74 100644
--- a/anvil-common/build.gradle
+++ b/anvil-common/build.gradle
@@ -8,6 +8,7 @@ plugins {
 dependencies {
     api(project(':anvil-api'))
 
+    api(bstats)
     api("net.kyori:adventure-text-serializer-legacy:4.5.0")
     api("net.kyori:adventure-text-serializer-plain:4.5.0")
     api(configurate_hocon)
diff --git a/anvil-common/src/main/java/org/anvilpowered/anvil/api/EnvironmentBuilderImpl.java b/anvil-common/src/main/java/org/anvilpowered/anvil/api/EnvironmentBuilderImpl.java
index c0c42ccf9..3f7e044fb 100644
--- a/anvil-common/src/main/java/org/anvilpowered/anvil/api/EnvironmentBuilderImpl.java
+++ b/anvil-common/src/main/java/org/anvilpowered/anvil/api/EnvironmentBuilderImpl.java
@@ -21,6 +21,7 @@
 import com.google.common.base.Preconditions;
 import com.google.common.reflect.TypeToken;
 import com.google.inject.AbstractModule;
+import com.google.inject.Binding;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Key;
@@ -32,6 +33,7 @@
 import org.anvilpowered.anvil.api.registry.Registry;
 import org.anvilpowered.anvil.api.registry.RegistryScope;
 import org.anvilpowered.anvil.common.PlatformImpl;
+import org.anvilpowered.anvil.common.metric.MetricService;
 import org.anvilpowered.anvil.common.module.PlatformModule;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -132,6 +134,11 @@ protected void configure() {
             }
             ServiceManagerImpl.environmentManager
                 .registerEnvironment(environment, environment.getPlugin());
+            Binding metricBinding =
+              Anvil.environment.getInjector().getExistingBinding(Key.get(MetricService.class));
+            if (metricBinding != null) {
+                metricBinding.getProvider().get().initialize(environment);
+            }
             for (Map.Entry, Consumer>> entry
                 : environment.getEarlyServices().entrySet()) {
                 ((Consumer) entry.getValue())
diff --git a/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java b/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java
index 37ffaf4fe..814f31e17 100644
--- a/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java
+++ b/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java
@@ -18,11 +18,16 @@
 
 package org.anvilpowered.anvil.common.plugin;
 
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
 import org.anvilpowered.anvil.api.plugin.PluginInfo;
+import org.anvilpowered.anvil.api.util.TextService;
 
-public class AnvilPluginInfo implements PluginInfo {
+import java.util.Map;
+
+public class AnvilPluginInfo implements PluginInfo {
     public static final String id = "anvil";
     public static final String name = "Anvil";
     public static final String version = "$modVersion";
@@ -36,6 +41,7 @@ public class AnvilPluginInfo implements PluginInfo {
       .append(Component.text(name, NamedTextColor.AQUA))
       .append(Component.text("] ", NamedTextColor.BLUE))
       .build();
+    public static final Map metricId = ImmutableMap.of();
 
     @Override
     public String getId() {
@@ -81,4 +87,9 @@ public String getBuildDate() {
     public Component getPrefix() {
         return pluginPrefix;
     }
+
+    @Override
+    public Map getMetricIds() {
+        return metricId;
+    }
 }
diff --git a/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/metric/MetricService.kt b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/metric/MetricService.kt
new file mode 100644
index 000000000..2c80e8b6d
--- /dev/null
+++ b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/metric/MetricService.kt
@@ -0,0 +1,30 @@
+/*
+ *   Anvil - AnvilPowered
+ *   Copyright (C) 2020-2021
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU Lesser General Public License as published by
+ *     the Free Software Foundation, either version 3 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU Lesser General Public License for more details.
+ *
+ *     You should have received a copy of the GNU Lesser General Public License
+ *     along with this program.  If not, see .
+ */
+
+package org.anvilpowered.anvil.common.metric
+
+import org.anvilpowered.anvil.api.Environment
+
+interface MetricService {
+
+  /**
+   * Initializes metrics through bStats for the specified [Environment]
+   */
+  fun initialize(env: Environment)
+
+}
diff --git a/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/misc/CommonBindingExtensions.kt b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/misc/CommonBindingExtensions.kt
new file mode 100644
index 000000000..39c1de1f5
--- /dev/null
+++ b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/misc/CommonBindingExtensions.kt
@@ -0,0 +1,97 @@
+/*
+ *   Anvil - AnvilPowered
+ *   Copyright (C) 2020-2021
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU Lesser General Public License as published by
+ *     the Free Software Foundation, either version 3 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU Lesser General Public License for more details.
+ *
+ *     You should have received a copy of the GNU Lesser General Public License
+ *     along with this program.  If not, see .
+ */
+
+package org.anvilpowered.anvil.common.misc
+
+import com.google.common.reflect.TypeToken
+import com.google.inject.Binder
+import com.google.inject.TypeLiteral
+import dev.morphia.Datastore
+import jetbrains.exodus.entitystore.EntityId
+import jetbrains.exodus.entitystore.PersistentEntityStore
+import org.anvilpowered.anvil.api.datastore.DBComponent
+import org.anvilpowered.anvil.api.datastore.DataStoreContext
+import org.anvilpowered.anvil.api.datastore.MongoContext
+import org.anvilpowered.anvil.api.datastore.XodusContext
+import org.anvilpowered.anvil.api.misc.BindingExtensions
+import org.bson.types.ObjectId
+
+@Suppress("unchecked", "UnstableApiUsage")
+class CommonBindingExtensions(val binder: Binder) : BindingExtensions {
+
+  override fun , From2 : DBComponent<*, *>, From3 : From1, Target : From1> bind(
+    from1: TypeToken,
+    from2: TypeToken,
+    from3: TypeToken,
+    target: TypeToken,
+    componentAnnotation: Annotation,
+  ) {
+    binder.bind(TypeLiteral.get(from2.type) as TypeLiteral)
+      .annotatedWith(componentAnnotation)
+      .to(BindingExtensions.getTypeLiteral(target))
+    binder.bind(TypeLiteral.get(from3.type) as TypeLiteral)
+      .to(BindingExtensions.getTypeLiteral(target))
+  }
+
+  override fun , From2 : From1, Target : From1> bind(
+    from1: TypeToken,
+    from2: TypeToken,
+    target: TypeToken,
+    componentAnnotation: Annotation,
+  ) {
+    bind(from1, from1, from2, target, componentAnnotation)
+  }
+
+  override fun  bind(
+    from: TypeToken,
+    target: TypeToken,
+    annotation: Annotation,
+  ) {
+    binder.bind(BindingExtensions.getTypeLiteral(from))
+      .annotatedWith(annotation)
+      .to(BindingExtensions.getTypeLiteral(target))
+  }
+
+  override fun  bind(
+    from: TypeToken,
+    target: TypeToken,
+    annotation: Class,
+  ) {
+    binder.bind(BindingExtensions.getTypeLiteral(from))
+      .annotatedWith(annotation)
+      .to(BindingExtensions.getTypeLiteral(target))
+  }
+
+  override fun  bind(
+    from: TypeToken,
+    target: TypeToken,
+  ) {
+    binder.bind(BindingExtensions.getTypeLiteral(from))
+      .to(BindingExtensions.getTypeLiteral(target))
+  }
+
+  override fun withMongoDB() {
+    binder.bind(object : TypeLiteral>() {})
+      .to(MongoContext::class.java)
+  }
+
+  override fun withXodus() {
+    binder.bind(object : TypeLiteral>() {})
+      .to(XodusContext::class.java)
+  }
+}
diff --git a/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/plugin/FallbackPluginInfo.kt b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/plugin/FallbackPluginInfo.kt
index 00348462e..144d8113e 100644
--- a/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/plugin/FallbackPluginInfo.kt
+++ b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/plugin/FallbackPluginInfo.kt
@@ -18,6 +18,7 @@
 
 package org.anvilpowered.anvil.common.plugin
 
+import com.google.common.collect.ImmutableMap
 import com.google.inject.Inject
 import net.kyori.adventure.text.Component
 import org.anvilpowered.anvil.api.Environment
@@ -32,6 +33,7 @@ class FallbackPluginInfo : PluginInfo {
         val authors = arrayOf("author")
         const val organizationName = "organizationName"
         const val buildDate = "last night"
+        val metricIds: Map = ImmutableMap.of("fake", 0)
     }
 
     @Inject
@@ -53,4 +55,5 @@ class FallbackPluginInfo : PluginInfo {
     override val organizationName: String = Companion.organizationName
     override val buildDate: String = Companion.buildDate
     override val prefix: Component = pluginPrefix
+    override val metricIds: Map = Companion.metricIds
 }
diff --git a/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/metric/SpigotMetricService.kt b/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/metric/SpigotMetricService.kt
new file mode 100644
index 000000000..af0ce8120
--- /dev/null
+++ b/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/metric/SpigotMetricService.kt
@@ -0,0 +1,115 @@
+/*
+ *   Anvil - AnvilPowered
+ *   Copyright (C) 2020-2021
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU Lesser General Public License as published by
+ *     the Free Software Foundation, either version 3 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU Lesser General Public License for more details.
+ *
+ *     You should have received a copy of the GNU Lesser General Public License
+ *     along with this program.  If not, see .
+ */
+
+package org.anvilpowered.anvil.spigot.metric
+
+import com.google.inject.Inject
+import java.io.File
+import java.io.IOException
+import java.util.UUID
+import org.anvilpowered.anvil.api.Environment
+import org.anvilpowered.anvil.api.util.UserService
+import org.anvilpowered.anvil.common.metric.MetricService
+import org.bstats.MetricsBase
+import org.bstats.json.JsonObjectBuilder
+import org.bukkit.Bukkit
+import org.bukkit.configuration.file.YamlConfiguration
+import org.bukkit.entity.Player
+import org.bukkit.plugin.Plugin
+import org.slf4j.Logger
+
+class SpigotMetricService @Inject constructor(
+  private val logger: Logger,
+  private val userService: UserService
+) : MetricService {
+
+  private lateinit var environment: Environment
+  private var metricsBase: MetricsBase? = null
+
+  override fun initialize(env: Environment) {
+    val serviceId: Int? = env.pluginInfo.metricIds["spigot"]
+    checkNotNull(serviceId) { "Could not find a valid Metrics Id for Spigot. Please check your PluginInfo" }
+    initialize(env, serviceId)
+  }
+
+  private fun initialize(environment: Environment, serviceId: Int) {
+    this.environment = environment
+    val bStatsFolder = File("plugins/bStats")
+    val configFile = File(bStatsFolder, "config.yml")
+    if (!bStatsFolder.exists()) {
+      require(configFile.mkdirs()) { "Could not create the bStats config!" }
+    }
+    val config: YamlConfiguration = YamlConfiguration.loadConfiguration(configFile)
+    if (!config.isSet("serverUuid")) {
+      config.addDefault("enabled", true)
+      config.addDefault("serverUuid", UUID.randomUUID().toString())
+      config.addDefault("logFailedRequests", false)
+      config.addDefault("logSentData", false)
+      config.addDefault("logResponseStatusText", false)
+
+      config.options().header(
+        """
+          bStats collects some data for plugin authors like how many servers are using their plugins.\n
+          To honor their work, you should not disable it.\n
+          This has nearly no effect on the server performance!\n
+          Check out https://bStats.org/ to learn more :)
+          """.trimIndent()
+      ).copyDefaults(true)
+      try {
+        config.save(configFile)
+      } catch (ignored: IOException) {
+      }
+    }
+
+    val enabled: Boolean = config.getBoolean("enabled", true)
+    val serverUUID: String = config.getString("serverUuid")!!
+    val logErrors: Boolean = config.getBoolean("logFailedRequests", false)
+    val logSentData: Boolean = config.getBoolean("logSentData", false)
+    val logResponseStatusText: Boolean = config.getBoolean("logResponseStatusText", false)
+    metricsBase = MetricsBase(
+      "bukkit",
+      serverUUID,
+      serviceId,
+      enabled,
+      { appendPlatformData(it) },
+      { appendServiceData(it) },
+      { Bukkit.getScheduler().runTask(environment.plugin as Plugin, it) },
+      { (environment.plugin as Plugin).isEnabled },
+      { message: String, error: Throwable -> logger.error(message, error) },
+      { logger.info(it) },
+      logErrors,
+      logSentData,
+      logResponseStatusText
+    )
+  }
+
+  private fun appendPlatformData(builder: JsonObjectBuilder) {
+    builder.appendField("playerAmount", getPlayerAmount())
+    builder.appendField("onlineMode", if (Bukkit.getOnlineMode()) 1 else 0)
+    builder.appendField("bukkitVersion", Bukkit.getVersion())
+    builder.appendField("bukkitName", Bukkit.getName())
+    builder.appendField("javaVersion", System.getProperty("java.version"))
+    builder.appendField("osName", System.getProperty("os.name"))
+    builder.appendField("osArch", System.getProperty("os.arch"))
+    builder.appendField("osVersion", System.getProperty("os.version"))
+    builder.appendField("coreCount", Runtime.getRuntime().availableProcessors())
+  }
+
+  private fun appendServiceData(builder: JsonObjectBuilder) = builder.appendField("pluginVersion", environment.pluginInfo.version)
+  private fun getPlayerAmount(): Int = userService.onlinePlayers.size
+}
diff --git a/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/module/ApiSpigotModule.kt b/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/module/ApiSpigotModule.kt
index 4e0eaa3a9..6d6e5c2fe 100644
--- a/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/module/ApiSpigotModule.kt
+++ b/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/module/ApiSpigotModule.kt
@@ -27,6 +27,7 @@ import org.anvilpowered.anvil.api.util.TextService
 import org.anvilpowered.anvil.api.util.UserService
 import org.anvilpowered.anvil.common.PlatformImpl
 import org.anvilpowered.anvil.common.entity.EntityUtils
+import org.anvilpowered.anvil.common.metric.MetricService
 import org.anvilpowered.anvil.common.module.JavaUtilLoggingAdapter
 import org.anvilpowered.anvil.common.module.PlatformModule
 import org.anvilpowered.anvil.common.util.CommonTextService
@@ -34,6 +35,7 @@ import org.anvilpowered.anvil.common.util.SendTextService
 import org.anvilpowered.anvil.spigot.command.SpigotCommandExecuteService
 import org.anvilpowered.anvil.spigot.command.SpigotSimpleCommandService
 import org.anvilpowered.anvil.spigot.entity.SpigotEntityUtils
+import org.anvilpowered.anvil.spigot.metric.SpigotMetricService
 import org.anvilpowered.anvil.spigot.server.SpigotLocationService
 import org.anvilpowered.anvil.spigot.util.SpigotKickService
 import org.anvilpowered.anvil.spigot.util.SpigotPermissionService
@@ -58,6 +60,7 @@ class ApiSpigotModule : PlatformModule(
     bind(KickService::class.java).to(SpigotKickService::class.java)
     bind(EntityUtils::class.java).to(SpigotEntityUtils::class.java)
     bind(LocationService::class.java).to(SpigotLocationService::class.java)
+    bind(MetricService::class.java).to(SpigotMetricService::class.java)
     bind(PermissionService::class.java).to(SpigotPermissionService::class.java)
     bind(object : TypeLiteral>() {}).to(SpigotSendTextService::class.java)
     bind(object : TypeLiteral>() {}).to(object : TypeLiteral>() {})
diff --git a/anvil-sponge/anvil-sponge-7/src/main/kotlin/org/anvilpowered/anvil/sponge7/metric/Sponge7MetricService.kt b/anvil-sponge/anvil-sponge-7/src/main/kotlin/org/anvilpowered/anvil/sponge7/metric/Sponge7MetricService.kt
new file mode 100644
index 000000000..d7b7d1705
--- /dev/null
+++ b/anvil-sponge/anvil-sponge-7/src/main/kotlin/org/anvilpowered/anvil/sponge7/metric/Sponge7MetricService.kt
@@ -0,0 +1,145 @@
+/*
+ *   Anvil - AnvilPowered
+ *   Copyright (C) 2020-2021
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU Lesser General Public License as published by
+ *     the Free Software Foundation, either version 3 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU Lesser General Public License for more details.
+ *
+ *     You should have received a copy of the GNU Lesser General Public License
+ *     along with this program.  If not, see .
+ */
+
+package org.anvilpowered.anvil.sponge7.metric
+
+import com.google.inject.Inject
+import java.io.File
+import java.io.IOException
+import java.nio.file.Path
+import java.util.UUID
+import ninja.leaping.configurate.commented.CommentedConfigurationNode
+import ninja.leaping.configurate.hocon.HoconConfigurationLoader
+import org.anvilpowered.anvil.api.Environment
+import org.anvilpowered.anvil.common.metric.MetricService
+import org.bstats.MetricsBase
+import org.bstats.json.JsonObjectBuilder
+import org.slf4j.Logger
+import org.spongepowered.api.Platform
+import org.spongepowered.api.Sponge
+import org.spongepowered.api.plugin.PluginContainer
+import org.spongepowered.api.scheduler.Scheduler
+import org.spongepowered.api.scheduler.Task
+
+class Sponge7MetricService @Inject constructor(
+  private val logger: Logger
+) : MetricService {
+
+  private lateinit var plugin: PluginContainer
+  private lateinit var configDir: Path
+  private var serviceId = 0
+  private lateinit var metricsBase: MetricsBase
+  private var serverUUID: String? = null
+  private var logErrors = false
+  private var logSentData = false
+  private var logResponseStatusText = false
+
+  override fun initialize(env: Environment) {
+    val serviceId: Int? = env.pluginInfo.metricIds["sponge"]
+    checkNotNull(serviceId) { "Could not find a valid Metrics Id for Sponge. Please check your PluginInfo" }
+
+    this.serviceId = serviceId
+    this.configDir = Sponge.getConfigManager().getSharedConfig(env.plugin).configPath
+    try {
+      loadConfig()
+    } catch (e: IOException) {
+      logger.warn("Failed to load bStats config!", e)
+      return
+    }
+    this.plugin = env.injector.getInstance(PluginContainer::class.java)
+    metricsBase = MetricsBase(
+      "sponge",
+      serverUUID,
+      serviceId,
+      Sponge.getMetricsConfigManager().getCollectionState(plugin).asBoolean(),
+      { builder: JsonObjectBuilder -> appendPlatformData(builder) },
+      { builder: JsonObjectBuilder -> appendServiceData(builder) },
+      { task: Runnable ->
+        val scheduler: Scheduler = Sponge.getScheduler()
+        val taskBuilder: Task.Builder = scheduler.createTaskBuilder()
+        taskBuilder.execute(task).submit(plugin)
+      },
+      { true },
+      logger::warn,
+      logger::info,
+      logErrors,
+      logSentData,
+      logResponseStatusText
+    )
+    val builder = StringBuilder()
+    builder.append("Plugin ").append(plugin.name).append(" is using bStats Metrics ")
+    if (Sponge.getMetricsConfigManager().getCollectionState(plugin).asBoolean()) {
+      builder.append(" and is allowed to send data.")
+    } else {
+      builder.append(" but currently has data sending disabled.").append(System.lineSeparator())
+      builder.append("To change the enabled/disabled state of any bStats use in a plugin, visit the Sponge config!")
+    }
+    logger.info(builder.toString())
+  }
+
+  private fun loadConfig() {
+    val configPath: File = configDir.resolve("bStats").toFile()
+    if (!configPath.exists()) {
+      if (!configPath.mkdirs()) {
+        logger.error("Could not create the bStats directory!")
+      }
+    }
+
+    val configFile = File(configPath, "config.conf")
+    val configurationLoader: HoconConfigurationLoader =
+      HoconConfigurationLoader.builder().setFile(configFile).build()
+
+    if (!configFile.exists()) {
+      require(configFile.mkdirs()) { "Could not create the bStats config!" }
+    }
+
+    val node: CommentedConfigurationNode = configurationLoader.load()
+    node.getNode("serverUuid").value = UUID.randomUUID().toString()
+    node.getNode("logFailedRequests").value = false
+    node.getNode("logSentData").value = false
+    node.getNode("logResponseStatusText").value = false
+    node.getNode("serverUuid").setComment(
+      "bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
+        "To control whether this is enabled or disabled, see the Sponge configuration file.\n" +
+        "Check out https://bStats.org/ to learn more :)"
+    )
+    node.getNode("configVersion").value = 2
+    configurationLoader.save(node)
+
+    serverUUID = node.getNode("serverUuid").string
+    logErrors = node.getNode("logFailedRequests").getBoolean(false)
+    logSentData = node.getNode("logSentData").getBoolean(false)
+    logResponseStatusText = node.getNode("logResponseStatusText").getBoolean(false)
+  }
+
+  private fun appendPlatformData(builder: JsonObjectBuilder) {
+    builder.appendField("playerAmount", Sponge.getServer().onlinePlayers.size)
+    builder.appendField("onlineMode", if (Sponge.getServer().onlineMode) 1 else 0)
+    builder.appendField("minecraftVersion", Sponge.getGame().platform.minecraftVersion.name)
+    builder.appendField("spongeImplementation", Sponge.getPlatform().getContainer(Platform.Component.IMPLEMENTATION).name)
+    builder.appendField("javaVersion", System.getProperty("java.version"))
+    builder.appendField("osName", System.getProperty("os.name"))
+    builder.appendField("osArch", System.getProperty("os.arch"))
+    builder.appendField("osVersion", System.getProperty("os.version"))
+    builder.appendField("coreCount", Runtime.getRuntime().availableProcessors())
+  }
+
+  private fun appendServiceData(builder: JsonObjectBuilder) {
+    builder.appendField("pluginVersion", plugin.version.orElse("unknown"))
+  }
+}
diff --git a/anvil-sponge/build.gradle b/anvil-sponge/build.gradle
index 5a136b397..fdeffceef 100644
--- a/anvil-sponge/build.gradle
+++ b/anvil-sponge/build.gradle
@@ -43,9 +43,11 @@ shadowJar {
 
         exclude("META-INF/versions/**")
         relocate("org.apache.commons", "relocated.apache")
+        relocate("org.bstats", "relocated.org.bstats")
         relocate("ninja.leaping", "relocated")
         include dependency(apache_commons)
         include dependency(bson)
+        include dependency(bstats)
         include dependency(configurate_core)
         include dependency(configurate_hocon)
         include dependency(javasisst)
diff --git a/anvil-velocity/build.gradle b/anvil-velocity/build.gradle
index 9439a75d7..726d48e8e 100644
--- a/anvil-velocity/build.gradle
+++ b/anvil-velocity/build.gradle
@@ -19,6 +19,7 @@ dependencies {
         implementation project(':Anvil:anvil-common')
     }
 
+    implementation bstats
     implementation javasisst
     implementation(kotlin_reflect + ":" + kotlin_version)
     implementation(kotlin_stdlib + ":" + kotlin_version)
@@ -45,8 +46,10 @@ shadowJar {
             include project(':Anvil:anvil-common')
         }
 
+        relocate("org.bstats", "relocated.org.bstats")
         include dependency(apache_commons)
         include dependency(bson)
+        include dependency(bstats)
         include dependency(javasisst)
         include dependency(jedis)
         include dependency(kotlin_reflect)
diff --git a/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/metric/VelocityMetricService.kt b/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/metric/VelocityMetricService.kt
new file mode 100644
index 000000000..ce3449317
--- /dev/null
+++ b/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/metric/VelocityMetricService.kt
@@ -0,0 +1,186 @@
+/*
+ *   Anvil - AnvilPowered
+ *   Copyright (C) 2020-2021
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU Lesser General Public License as published by
+ *     the Free Software Foundation, either version 3 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU Lesser General Public License for more details.
+ *
+ *     You should have received a copy of the GNU Lesser General Public License
+ *     along with this program.  If not, see .
+ */
+
+package org.anvilpowered.anvil.velocity.metric
+
+import com.google.inject.Inject
+import com.velocitypowered.api.proxy.ProxyServer
+import java.io.BufferedReader
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileReader
+import java.io.FileWriter
+import java.io.IOException
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.util.Optional
+import java.util.UUID
+import java.util.regex.Pattern
+import java.util.stream.Collectors
+import org.anvilpowered.anvil.api.Environment
+import org.anvilpowered.anvil.api.Platform
+import org.anvilpowered.anvil.api.server.LocationService
+import org.anvilpowered.anvil.common.metric.MetricService
+import org.bstats.MetricsBase
+import org.bstats.json.JsonObjectBuilder
+import org.slf4j.Logger
+
+class VelocityMetricService @Inject constructor(
+  private val logger: Logger,
+  private val platform: Platform,
+  private val proxyServer: ProxyServer,
+  private val locationService: LocationService,
+) : MetricService {
+
+  private lateinit var metricsBase: MetricsBase
+  private var serverUUID: String? = null
+  private var enabled: Boolean = false
+  private var logErrors: Boolean = false
+  private var logSentData: Boolean = false
+  private var logResponseStatusText: Boolean = false
+  private lateinit var dataDirectory: Path
+  private lateinit var environment: Environment
+
+  override fun initialize(env: Environment) {
+    val serviceId: Int? = env.pluginInfo.metricIds["velocity"]
+    requireNotNull(serviceId) { "Could not find a valid Metrics Id for Velocity. Please check your PluginInfo" }
+    initialize(env, Paths.get("plugins/bStats"), serviceId)
+  }
+
+  private fun initialize(environment: Environment, dataPath: Path, serviceId: Int) {
+    this.dataDirectory = dataPath
+    this.environment = environment
+    try {
+      setupConfig(true)
+    } catch (e: IOException) {
+      logger.error("Failed to create bStats config", e)
+    }
+    metricsBase = MetricsBase(
+      platform.name,
+      serverUUID,
+      serviceId,
+      enabled,
+      this::appendPlatformData,
+      this::appendServiceData,
+      { task -> proxyServer.scheduler.buildTask(environment.plugin, task).schedule() },
+      { true },
+      logger::warn,
+      logger::info,
+      logErrors,
+      logSentData,
+      logResponseStatusText
+    )
+  }
+
+  private fun setupConfig(recreateWhenMalformed: Boolean) {
+    val configDir = dataDirectory.parent.resolve("bStats").toFile()
+    if (!configDir.exists()) {
+      require(configDir.mkdirs()) {"Could not create the bStats config!"}
+    }
+    val configFile = File(configDir, "config.txt")
+    if (!configFile.exists()) {
+      writeConfig(configFile)
+    }
+
+    val lines: List = readFile(configFile)
+
+    enabled = getConfigValue("enabled", lines).map { anObject: String -> "true" == anObject }.orElse(true)
+    serverUUID = getConfigValue("server-uuid", lines).orElse(null)
+    logErrors = getConfigValue("log-errors", lines).map { anObject: String -> "true" == anObject }.orElse(false)
+    logSentData = getConfigValue("log-sent-data", lines).map { anObject: String -> "true" == anObject }.orElse(false)
+    logResponseStatusText = getConfigValue("log-response-status-text", lines)
+      .map { anObject: String -> "true" == anObject }.orElse(false)
+
+    if (serverUUID == null) {
+      if (recreateWhenMalformed) {
+        logger.info("Found malformed bStats config file. Re-creating it...")
+        if (!configFile.delete()) {
+          logger.error("Could not delete the bStats config!")
+          return
+        }
+        setupConfig(false)
+      } else {
+        logger.error("Failed to re-create malformed bStats config file")
+        return
+      }
+    }
+  }
+
+  private fun writeConfig(file: File) {
+    val configContent: MutableList = ArrayList()
+    configContent.add("# bStats collects some basic information for plugin authors, like how many people use")
+    configContent.add("# their plugin and their total player count. It's recommend to keep bStats enabled, but")
+    configContent.add("# if you're not comfortable with this, you can turn this setting off. There is no")
+    configContent.add("# performance penalty associated with having metrics enabled, and data sent to bStats")
+    configContent.add("# can't identify your server.")
+    configContent.add("enabled=true")
+    configContent.add("server-uuid=" + UUID.randomUUID().toString())
+    configContent.add("log-errors=false")
+    configContent.add("log-sent-data=false")
+    configContent.add("log-response-status-text=false")
+    writeFile(file, configContent)
+  }
+
+  private fun getConfigValue(key: String, lines: List): Optional {
+    return lines.stream()
+      .filter { it.startsWith("$key=") }
+      .map { it.replaceFirst(Pattern.quote("$key=").toRegex(), "") }
+      .findFirst()
+  }
+
+  private fun readFile(file: File): List {
+    if (!file.exists()) {
+      return emptyList()
+    }
+    FileReader(file).use { BufferedReader(it).use { o -> return o.lines().collect(Collectors.toList()) } }
+  }
+
+  private fun writeFile(file: File, lines: List) {
+    if (!file.exists()) {
+      if (!file.createNewFile()) {
+        logger.error("Could not create the config file for bStats!")
+        return
+      }
+    }
+    FileWriter(file).use {
+      BufferedWriter(it).use { o ->
+        for (line in lines) {
+          o.write(line)
+          o.newLine()
+        }
+      }
+    }
+  }
+
+  private fun appendPlatformData(builder: JsonObjectBuilder) {
+    builder.appendField("playerAmount", proxyServer.playerCount)
+    builder.appendField("managedServers", locationService.getServers().size)
+    builder.appendField("onlineMode", if (proxyServer.configuration.isOnlineMode) 1 else 0)
+    builder.appendField("velocityVersionVersion", proxyServer.version.version)
+    builder.appendField("velocityVersionName", proxyServer.version.name)
+    builder.appendField("velocityVersionVendor", proxyServer.version.vendor)
+    builder.appendField("javaVersion", System.getProperty("java.version"))
+    builder.appendField("osName", System.getProperty("os.name"))
+    builder.appendField("osArch", System.getProperty("os.arch"))
+    builder.appendField("osVersion", System.getProperty("os.version"))
+    builder.appendField("coreCount", Runtime.getRuntime().availableProcessors())
+  }
+
+  private fun appendServiceData(builder: JsonObjectBuilder) =
+    builder.appendField("pluginVersion", environment.pluginInfo.version)
+}
diff --git a/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/module/ApiVelocityModule.kt b/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/module/ApiVelocityModule.kt
index a0e3e0963..86414a6cf 100644
--- a/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/module/ApiVelocityModule.kt
+++ b/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/module/ApiVelocityModule.kt
@@ -31,11 +31,13 @@ import org.anvilpowered.anvil.api.util.TextService
 import org.anvilpowered.anvil.api.util.UserService
 import org.anvilpowered.anvil.common.PlatformImpl
 import org.anvilpowered.anvil.common.command.CommonCallbackCommand
+import org.anvilpowered.anvil.common.metric.MetricService
 import org.anvilpowered.anvil.common.module.PlatformModule
 import org.anvilpowered.anvil.common.util.CommonTextService
 import org.anvilpowered.anvil.common.util.SendTextService
 import org.anvilpowered.anvil.velocity.command.VelocityCommandExecuteService
 import org.anvilpowered.anvil.velocity.command.VelocitySimpleCommandService
+import org.anvilpowered.anvil.velocity.metric.VelocityMetricService
 import org.anvilpowered.anvil.velocity.server.VelocityLocationService
 import org.anvilpowered.anvil.velocity.util.VelocityKickService
 import org.anvilpowered.anvil.velocity.util.VelocityPermissionService
@@ -57,6 +59,7 @@ class ApiVelocityModule : PlatformModule(
       bind>().to()
       bind().to()
       bind().to()
+      bind().to()
       bind().to()
       bind>().to>()
       bind>().to>()
diff --git a/gradle.properties b/gradle.properties
index 70fd21b0a..f57556bae 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,6 +2,7 @@
 aopalliance=aopalliance:aopalliance:1.0
 apache_commons=org.apache.commons:commons-pool2:2.6.2
 bson=org.mongodb:bson:3.12.0
+bstats=org.bstats:bstats-base:2.1.0
 bungee=net.md-5:bungeecord-api:1.15-SNAPSHOT
 configurate_core=org.spongepowered:configurate-core:3.7.2
 configurate_hocon=org.spongepowered:configurate-hocon:3.7.2