Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ gem 'newrelic_rpm'
gem 'nokogiri', '>=1.10.5'
gem 'oj'
gem 'openssl', '>= 3.2'
gem 'palm_civet'
gem 'prometheus-client'
gem 'public_suffix'
gem 'puma'
Expand Down
2 changes: 1 addition & 1 deletion lib/cloud_controller/app_manifest/byte_converter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require 'palm_civet'
require 'cloud_controller/palm_civet'

module VCAP::CloudController
class ByteConverter
Expand Down
91 changes: 91 additions & 0 deletions lib/cloud_controller/palm_civet.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
module VCAP
module CloudController
module PalmCivet
BYTE = 1.0
KILOBYTE = 1024 * BYTE
MEGABYTE = 1024 * KILOBYTE
GIGABYTE = 1024 * MEGABYTE
TERABYTE = 1024 * GIGABYTE
BYTESPATTERN = /^(-?\d+(?:\.\d+)?)([KMGT]i?B?|B)$/i

class InvalidByteQuantityError < RuntimeError
def initialize(msg="byte quantity must be a positive integer with a unit of measurement like M, MB, MiB, G, GiB, or GB")
super
end
end

# Returns a human-readable byte string of the form 10M, 12.5K, and so forth.
# The following units are available:
# * T: Terabyte
# * G: Gigabyte
# * M: Megabyte
# * K: Kilobyte
# * B: Byte
# The unit that results in the smallest number greater than or equal to 1 is
# always chosen.
def self.byte_size(bytes)
if !bytes.is_a? Numeric
raise TypeError, "must be an integer or float"
end

case
when bytes >= TERABYTE
unit = "T"
value = bytes / TERABYTE
when bytes >= GIGABYTE
unit = "G"
value = bytes / GIGABYTE
when bytes >= MEGABYTE
unit = "M"
value = bytes / MEGABYTE
when bytes >= KILOBYTE
unit = "K"
value = bytes / KILOBYTE
when bytes >= BYTE
unit = "B"
value = bytes
else
return "0"
end

value = "%g" % ("%.1f" % value)
return value << unit
end

# Parses a string formatted by bytes_size as bytes. Note binary-prefixed and
# SI prefixed units both mean a base-2 units:
# * KB = K = KiB = 1024
# * MB = M = MiB = 1024 * K
# * GB = G = GiB = 1024 * M
# * TB = T = TiB = 1024 * G
def self.to_bytes(bytes)
matches = BYTESPATTERN.match(bytes.strip)
if matches == nil
raise InvalidByteQuantityError
end

value = Float(matches[1])

case matches[2][0].capitalize
when "T"
value = value * TERABYTE
when "G"
value = value * GIGABYTE
when "M"
value = value * MEGABYTE
when "K"
value = value * KILOBYTE
end

return value.to_i
rescue TypeError
raise InvalidByteQuantityError
end

# Parses a string formatted by byte_size as megabytes.
def self.to_megabytes(bytes)
(self.to_bytes(bytes) / MEGABYTE).to_i
end
end
end
end
161 changes: 161 additions & 0 deletions spec/unit/lib/cloud_controller/palm_civet_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
require 'spec_helper'
require 'cloud_controller/palm_civet'

module VCAP::CloudController
RSpec.describe PalmCivet do
describe "#byte_size" do
it "prints in the largest possible unit" do
expect(PalmCivet.byte_size(10 * PalmCivet::TERABYTE)).to eq("10T")
expect(PalmCivet.byte_size(10.5 * PalmCivet::TERABYTE)).to eq("10.5T")

expect(PalmCivet.byte_size(10 * PalmCivet::GIGABYTE)).to eq("10G")
expect(PalmCivet.byte_size(10.5 * PalmCivet::GIGABYTE)).to eq("10.5G")

expect(PalmCivet.byte_size(100 * PalmCivet::MEGABYTE)).to eq("100M")
expect(PalmCivet.byte_size(100.5 * PalmCivet::MEGABYTE)).to eq("100.5M")

expect(PalmCivet.byte_size(100 * PalmCivet::KILOBYTE)).to eq("100K")
expect(PalmCivet.byte_size(100.5 * PalmCivet::KILOBYTE)).to eq("100.5K")

expect(PalmCivet.byte_size(1)).to eq("1B")
end

it "prints '0' for zero bytes" do
expect(PalmCivet.byte_size(0)).to eq("0")
end

it "raises a type error on non-number values" do
expect {
PalmCivet.byte_size("something else")
}.to raise_error(TypeError, "must be an integer or float")
end
end

describe "#to_bytes" do
it "parses byte amounts with short units (e.g. M, G)" do
expect(PalmCivet.to_bytes("5B")).to eq(5)
expect(PalmCivet.to_bytes("5K")).to eq(5 * PalmCivet::KILOBYTE)
expect(PalmCivet.to_bytes("5M")).to eq(5 * PalmCivet::MEGABYTE)
expect(PalmCivet.to_bytes("5G")).to eq(5 * PalmCivet::GIGABYTE)
expect(PalmCivet.to_bytes("5T")).to eq(5 * PalmCivet::TERABYTE)
end

it "parses byte amounts that are float (e.g. 5.3KB)" do
expect(PalmCivet.to_bytes("13.5KB")).to eq(13824)
expect(PalmCivet.to_bytes("4.5KB")).to eq(4608)
expect(PalmCivet.to_bytes("2.55KB")).to eq(2611)
end

it "parses byte amounts with long units (e.g MB, GB)" do
expect(PalmCivet.to_bytes("5MB")).to eq(5 * PalmCivet::MEGABYTE)
expect(PalmCivet.to_bytes("5mb")).to eq(5 * PalmCivet::MEGABYTE)
expect(PalmCivet.to_bytes("2GB")).to eq(2 * PalmCivet::GIGABYTE)
expect(PalmCivet.to_bytes("3TB")).to eq(3 * PalmCivet::TERABYTE)
end

it "parses byte amounts with long binary units (e.g MiB, GiB)" do
expect(PalmCivet.to_bytes("5MiB")).to eq(5 * PalmCivet::MEGABYTE)
expect(PalmCivet.to_bytes("5mib")).to eq(5 * PalmCivet::MEGABYTE)
expect(PalmCivet.to_bytes("2GiB")).to eq(2 * PalmCivet::GIGABYTE)
expect(PalmCivet.to_bytes("3TiB")).to eq(3 * PalmCivet::TERABYTE)
end

it "allows whitespace before and after the value" do
expect(PalmCivet.to_bytes("\t\n\r 5MB ")).to eq(5 * PalmCivet::MEGABYTE)
end

context 'when the byte amount is 0' do
it 'returns 0 bytes' do
expect(PalmCivet.to_bytes("0TB")).to eq(0)
end
end

context 'when the byte amount is negative' do
it 'returns a negative amount of bytes' do
expect(PalmCivet.to_bytes('-200B')).to eq(-200)
end
end

context "raises an error" do
it "when the unit is missing" do
expect {
PalmCivet.to_bytes("5")
}.to raise_error(PalmCivet::InvalidByteQuantityError)
end

it "when the unit is unrecognized" do
expect {
PalmCivet.to_bytes("5MBB")
}.to raise_error(PalmCivet::InvalidByteQuantityError)

expect {
PalmCivet.to_bytes("5BB")
}.to raise_error(PalmCivet::InvalidByteQuantityError)
end
end
end

describe "#to_megabytes" do
it "parses byte amounts with short units (e.g. M, G)" do
expect(PalmCivet.to_megabytes("5B")).to eq(0)
expect(PalmCivet.to_megabytes("5K")).to eq(0)
expect(PalmCivet.to_megabytes("5M")).to eq(5)
expect(PalmCivet.to_megabytes("5m")).to eq(5)
expect(PalmCivet.to_megabytes("5G")).to eq(5120)
expect(PalmCivet.to_megabytes("5T")).to eq(5242880)
end

it "parses byte amounts with long units (e.g MB, GB)" do
expect(PalmCivet.to_megabytes("5B")).to eq(0)
expect(PalmCivet.to_megabytes("5KB")).to eq(0)
expect(PalmCivet.to_megabytes("5MB")).to eq(5)
expect(PalmCivet.to_megabytes("5mb")).to eq(5)
expect(PalmCivet.to_megabytes("5GB")).to eq(5120)
expect(PalmCivet.to_megabytes("5TB")).to eq(5242880)
end

it "parses byte amounts with long binary units (e.g MiB, GiB)" do
expect(PalmCivet.to_megabytes("5B")).to eq(0)
expect(PalmCivet.to_megabytes("5KiB")).to eq(0)
expect(PalmCivet.to_megabytes("5MiB")).to eq(5)
expect(PalmCivet.to_megabytes("5mib")).to eq(5)
expect(PalmCivet.to_megabytes("5GiB")).to eq(5120)
expect(PalmCivet.to_megabytes("5TiB")).to eq(5242880)
end

it "allows whitespace before and after the value" do
expect(PalmCivet.to_megabytes("\t\n\r 5MB ")).to eq(5)
end

context 'when the byte amount is 0' do
it 'returns 0 megabytes' do
expect(PalmCivet.to_megabytes('0TB')).to eq(0)
end
end

context 'when the byte amount is negative' do
it 'returns a negative amount of megabytes' do
expect(PalmCivet.to_megabytes('-200MB')).to eq(-200)
end
end

context "raises an error" do
it "when the unit is missing" do
expect {
PalmCivet.to_megabytes("5")
}.to raise_error(PalmCivet::InvalidByteQuantityError)
end

it "when the unit is unrecognized" do
expect {
PalmCivet.to_megabytes("5MBB")
}.to raise_error(PalmCivet::InvalidByteQuantityError)

expect {
PalmCivet.to_megabytes("5BB")
}.to raise_error(PalmCivet::InvalidByteQuantityError)
end
end
end
end
end
Loading