Export attachment count via JMX

This commit is contained in:
Mike Hearn 2016-03-11 16:59:50 +01:00
parent 730b7949ea
commit 28869ad85d
3 changed files with 36 additions and 20 deletions

View File

@ -211,6 +211,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
Files.createDirectory(attachmentsDir)
} catch (e: FileAlreadyExistsException) {
}
return NodeAttachmentService(attachmentsDir)
return NodeAttachmentService(attachmentsDir, services.monitoringService.metrics)
}
}

View File

@ -8,6 +8,7 @@
package core.node.services
import com.codahale.metrics.MetricRegistry
import com.google.common.annotations.VisibleForTesting
import com.google.common.hash.Hashing
import com.google.common.hash.HashingInputStream
@ -31,12 +32,21 @@ import javax.annotation.concurrent.ThreadSafe
* Stores attachments in the specified local directory, which must exist. Doesn't allow new attachments to be uploaded.
*/
@ThreadSafe
class NodeAttachmentService(val storePath: Path) : AttachmentStorage, AcceptsFileUpload {
class NodeAttachmentService(val storePath: Path, val metrics: MetricRegistry) : AttachmentStorage, AcceptsFileUpload {
private val log = loggerFor<NodeAttachmentService>()
@VisibleForTesting
var checkAttachmentsOnLoad = true
private val attachmentCount = metrics.counter("Attachments")
init {
attachmentCount.inc(countAttachments())
}
// Just count all non-directories in the attachment store, and assume the admin hasn't dumped any junk there.
private fun countAttachments() = Files.list(storePath).filter { Files.isRegularFile(it) }.count()
/**
* If true, newly inserted attachments will be unzipped to a subdirectory of the [storePath]. This is intended for
* human browsing convenience: the attachment itself will still be the file (that is, edits to the extracted directory
@ -77,22 +87,25 @@ class NodeAttachmentService(val storePath: Path) : AttachmentStorage, AcceptsFil
}
}
// Deliberately not an inner class to avoid holding a reference to the attachments service.
private class AttachmentImpl(override val id: SecureHash,
private val path: Path,
private val checkOnLoad: Boolean) : Attachment {
override fun open(): InputStream {
var stream = Files.newInputStream(path)
// This is just an optional safety check. If it slows things down too much it can be disabled.
if (id is SecureHash.SHA256 && checkOnLoad)
stream = HashCheckingStream(id, path, stream)
return stream
}
override fun equals(other: Any?) = other is Attachment && other.id == id
override fun hashCode(): Int = id.hashCode()
}
override fun openAttachment(id: SecureHash): Attachment? {
val path = storePath.resolve(id.toString())
if (!Files.exists(path)) return null
return object : Attachment {
override fun open(): InputStream {
var stream = Files.newInputStream(path)
// This is just an optional safety check. If it slows things down too much it can be disabled.
if (id is SecureHash.SHA256 && checkAttachmentsOnLoad)
stream = HashCheckingStream(id, path, stream)
log.debug("Opening attachment $id")
return stream
}
override val id: SecureHash = id
override fun equals(other: Any?) = other is Attachment && other.id == id
override fun hashCode(): Int = id.hashCode()
}
return AttachmentImpl(id, path, checkAttachmentsOnLoad)
}
override fun importAttachment(jar: InputStream): SecureHash {
@ -106,10 +119,12 @@ class NodeAttachmentService(val storePath: Path) : AttachmentStorage, AcceptsFil
try {
// Move into place atomically or fail if that isn't possible. We don't want a half moved attachment to
// be exposed to parallel threads. This gives us thread safety.
if (!Files.exists(finalPath))
if (!Files.exists(finalPath)) {
log.info("Stored new attachment $id")
else
attachmentCount.inc()
} else {
log.info("Replacing attachment $id - only bother doing this if you're trying to repair file corruption")
}
Files.move(tmp, finalPath, StandardCopyOption.ATOMIC_MOVE)
} finally {
Files.deleteIfExists(tmp)

View File

@ -8,6 +8,7 @@
package core.node
import com.codahale.metrics.MetricRegistry
import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs
import core.crypto.SecureHash
@ -40,7 +41,7 @@ class NodeAttachmentStorageTest {
val testJar = makeTestJar()
val expectedHash = SecureHash.sha256(Files.readAllBytes(testJar))
val storage = NodeAttachmentService(fs.getPath("/"))
val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry())
val id = testJar.use { storage.importAttachment(it) }
assertEquals(expectedHash, id)
@ -57,7 +58,7 @@ class NodeAttachmentStorageTest {
@Test
fun `duplicates not allowed`() {
val testJar = makeTestJar()
val storage = NodeAttachmentService(fs.getPath("/"))
val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry())
testJar.use { storage.importAttachment(it) }
assertFailsWith<java.nio.file.FileAlreadyExistsException> {
testJar.use { storage.importAttachment(it) }
@ -67,7 +68,7 @@ class NodeAttachmentStorageTest {
@Test
fun `corrupt entry throws exception`() {
val testJar = makeTestJar()
val storage = NodeAttachmentService(fs.getPath("/"))
val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry())
val id = testJar.use { storage.importAttachment(it) }
// Corrupt the file in the store.