Skip to content
This repository was archived by the owner on Dec 20, 2021. It is now read-only.

Initialize dynamic-sampler #1

Merged
merged 3 commits into from
Mar 2, 2018
Merged

Initialize dynamic-sampler #1

merged 3 commits into from
Mar 2, 2018

Conversation

ChristopherBiscardi
Copy link
Contributor

Implement two samplers, PerKey and Avg, with the same interface. Check
README for how to create new samplers and other docs.

Public API is to instantiate a new sampler and then use getSampleRate(key).

const sampler = new SomeSampler()
sampler.getSampleRate("some-key")

Implement two samplers, PerKey and Avg, with the same interface. Check
README for how to create new samplers and other docs.

Public API is to instantiate a new sampler and then use `getSampleRate(key)`.

```javascript
const sampler = new SomeSampler()
sampler.getSampleRate("some-key")
```
@@ -0,0 +1,52 @@
jest.mock("nanotimer");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line isn't required but makes it nice for the next person who might be unfamiliar with mocks and why nanotimer now has a .tick() function but only in tests.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 this line in general. better to be explicit for future readers.

Copy link
Member

@eanakashima eanakashima left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dogjumping

Yay, excited about this! A few questions in line but nothing that seems like a blocker. Haven't had a chance to pull it down to test out, but the API all makes sense to me.

}
}
}
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love all the code examples in the README. 😍

Not a blocker for shipping this at all, but at some point pulling in more of the verbiage from the dynsampler-go readme — esp the "what is sampling" bits from the top — could be handy. Want me to open issues for readme nice-to-haves like that? (The other one I'm thinking of is the answer to "can I use this in a browser?" 😳)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes please! Open issues and I'll knock em down.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay, will do.

README.md Outdated
});
this.savedSampleRates = newRates;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the example invocation in the source comment was helpful — maybe worth having that in this example too?

const sampler = new PerKey({clearFrequencySec: 100, perKeyThroughputSec: 2})

Helps document the config options too.

index.test.js Outdated

test("gets a sample rate", () => {
const sampler = new PerKey();
new Array(1500).fill(1).forEach(() => sampler.getSampleRate("my-key"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason behind 1500 here? Just curious.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just needed a value that I can predict the sample rate of

package.json Outdated
"repository": "https://github.com/honeycombio/dynamic-sampler.js",
"author":
"Christopher Biscardi <[email protected]> (@chrisbiscardi)",
"license": "MIT",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to resurrect our chat bikeshed on this topic, but thoughts on using Apache 2.0 for consistency with our other repos, for now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure.

For later discussion: This is the default boilerplate for yarn init and npm init though so we should write down the reasoning for "why apache 2.0" somewhere since this is the community default for JS.

// No traffic means this is the goal sample rate
expect(a).toEqual(10);
sampler.timer.tick();
expect(sampler.getSampleRate("my-key")).toEqual(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, but it takes me a fair amount of mental context threading to see how the log math above leads to this set of expectations... maybe a few more comments or something a little more verbose would be helpful? (The dynsampler-ruby equivalent feels more parseable somehow.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious if it's the table-based approach in the ruby tests that makes it more parseable

Cross that, the ruby test looks like we're reaching into the internals to set the rate and it's not actually triggering an updateMap so wouldn't have the additional ticks. For comparison, this test is only using the public API with a mock for timers so we can manually trigger the updateMap() through the timer callback.

Curious as to your opinion on the difference in the tests. This test feels like it approximates more real-world usage

  1. create a sampler
  2. throw traffic at it
  3. the timer callback triggers updateMaps
  4. get the sample rate

Whereas the ruby test seems like:

  1. Reach into internals to set internal properties
  2. Check that those internal properties match what we'd expect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh, when did Shift+Enter become "commit comment". Too much time in Slack.

Do you think adding the "stage" comments above is enough? or should I try to do something else.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, fair point, agree that it's a bit nicer to focus on testing the public API. 👍for adding the stage comments for now and I'll noodle more on what my dream tests would look like.

Copy link

@toshok toshok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

general bikeshed comment:

  • dynsampler-js
  • AvgSampleRate + PerKeyThroughput
  • ideally dynsampler for the package name, but a lot more leeway for package idioms there.

@@ -0,0 +1,52 @@
jest.mock("nanotimer");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 this line in general. better to be explicit for future readers.

index.js Outdated
export class PerKey extends Sampler {
constructor(opts = {}) {
super(opts);
this.perKeyThroughputSec = opts.perKeyThroughputSec || 5;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both go and ruby default to 10 here.

index.js Outdated
updateMaps() {
debug("PerKey.updateMaps()", this.id && this.id);
if (this.currentCounts.size == 0) {
//no traffic in the last 30s. clear the result Map
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suuuuuper minor nit. s/30s/clearFrequencySec/

README.md Outdated

const newRates = new Map();
this.currentCounts.forEach((val, key) => {
newRates.set(key, Math.max(1, val / actualPerKeyRate));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both ruby and golang coerce the value to an int. :shakes fist: at JS. any reason not to do that here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, going to go with Math.floor to match golang.

index.js Outdated

const newRates = new Map();
this.currentCounts.forEach((val, key) => {
newRates.set(key, Math.max(1, val / actualPerKeyRate));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here wrt coercing to an int

});
const goalCount = sumEvents / this.goalSampleRate;
const goalRatio = goalCount / logSum;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both the go/ruby implementations sort the keys here and iterate in sorted order. my head hurts looking the ruby implementation. I expect it's a problem to drop the sort, but ... /shrug?

Copy link
Contributor Author

@ChristopherBiscardi ChristopherBiscardi Feb 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're using Maps I'm pretty sure we're good here, unless we need a specific kind of sort?

The forEach() method executes a provided function once per each key/value pair in the Map object, in insertion order.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that's the question I had - if we give the dynsamplers the same input, do we expect the same results? both other implementations go beyond the "sort them in the same order for this implementation to fix rounding errors" and use a sort that should be the same across implementations.

eh, we can deal with that when it's an issue for someone (e.g. "I'm using two dynsampler implementations and things are different between them"). And the answer even then might be WONTFIX.

} else {
// TODO: how well supported is this syntax
// (basically no IE native support. what about babel/etc?)
return super.getSampleRate(key);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope babel supports it. I've been writing code for a diff that uses it 😬

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it does. My concern was centered more at what buble does and whether we should care about that before microbundle gets it's babel support: developit/microbundle#39.

Copy link
Member

@eanakashima eanakashima left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, +1 "dynsampler-js" as the project name, can't believe I didn't look at that 🤦‍♂️. There have been recent efforts to make our herd of open source projects a little better at following naming conventions, so seems like a good chance to establish a convention here too.

}
}
}
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay, will do.

// No traffic means this is the goal sample rate
expect(a).toEqual(10);
sampler.timer.tick();
expect(sampler.getSampleRate("my-key")).toEqual(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, fair point, agree that it's a bit nicer to focus on testing the public API. 👍for adding the stage comments for now and I'll noodle more on what my dream tests would look like.

@ChristopherBiscardi
Copy link
Contributor Author

ChristopherBiscardi commented Mar 1, 2018

@toshok Are there still changes here you want to see?

I'd like to merge it and move on to more work like setting up Travis, etc.

@toshok
Copy link

toshok commented Mar 2, 2018

thanks for the ping. other than repo renaming, this LGTM

@ChristopherBiscardi ChristopherBiscardi merged commit 8ed199f into master Mar 2, 2018
@robbkidd robbkidd deleted the first-push branch January 27, 2021 23:43
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants