Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for zero-setup object method profiling? #2

Open
nietaki opened this issue Aug 12, 2024 · 0 comments
Open

Support for zero-setup object method profiling? #2

nietaki opened this issue Aug 12, 2024 · 0 comments
Labels
enhancement New feature or request

Comments

@nietaki
Copy link
Contributor

nietaki commented Aug 12, 2024

This is a vague proposal / food for thought / example of how I use AppleCake. It might not make sense to add this functionality to AppleCake itself, but I would like to at least share what I did - maybe it's going to be useful to somebody else.


Ever since I started using AppleCake more extensively, I realised it became a bit of a pain to wrangle the profile objects and to make sure the methods on different objects weren't re-using calls of the same method on different objects or recursive calls on the same method.

I realise the OO approach isn't a first-class thing in Lua and there's many ways to get it (I'm using middleclass right now), but it was an important enough use-case for me that I built up an AppleCake utility mixin to handle that.

Here's how I use it in real code:

ShaderStructArray = class('ShaderStructArray')

-- object fields to include in the profile args
ProfiledObject.includeIn(ShaderStructArray, {fields = {'name'}})

-- example method
function ShaderStructArray:markUnchanged()
  self:trace()
  for _, struct in ipairs(self._elements) do
    struct:markUnchanged()
  end
  self:untrace()
end

This allows me to easily get more complicated traces like this one:
Screenshot 2024-07-30 at 21 07 20

There's probably a better and more generic way of implementing it, but here's the code for the ProfiledObject mixin (you'll find some chunks of it stolen from the AppleCake implementation):

local function _methodNameAndPath(self)
  local className = self.class.name or 'UnknownClass'
  local name = 'unknownName'
  local path = 'unknownPath'
  local info = debug.getinfo(3, "fnS")
  local line = ""
  if info then
    if info.name then
      name = info.name
    elseif info.func then -- Attempt to create a name from memory address
      name = tostring(info.func):sub(10)
    else
      error("Could not generate name for this function")
    end
    if info.short_src then
      line = (info.linedefined and "#"..info.linedefined or line)
      path = info.short_src..line
    end
  end
  -- return className .. '.' .. name .. '[' .. line .. ']', path
  return string.format('%s.%s[%s]', className, name, line), path
end

local profilesFieldName = '_objectProfiles'

local function _profiles(self)
  local profiles = rawget(self, profilesFieldName)
  if not profiles then
    profiles = {}
    rawset(self, profilesFieldName, profiles)
  end
  return profiles
end

local function buildArgs(self, args)
  args = args or {}
  if self.class.profiledFieldNames then
    for _, fieldName in ipairs(self.class.profiledFieldNames) do
      args[fieldName] = rawget(self, fieldName)
    end
  end
  return args
end

local function _profileName(baseName, recursionDepth)
  if recursionDepth and recursionDepth > 0 then
    -- baseName ..
    return baseName .. '_level_' .. tostring(recursionDepth)
  else
    return baseName
  end
end

local function startProfile(self, args, recursionDepth)
  local name, path = _methodNameAndPath(self)
  args = buildArgs(self, args)
  -- print('method', _methodNameAndPath(self))
  args.path = path

  local profileName = _profileName(name, recursionDepth)
  local profile = _profiles(self)[profileName]
  profile = AppleCake.profile(name, args, profile)
  _profiles(self)[profileName] = profile
end

local function mark(self, name, subname)
  name = tostring(name)
  if subname then
    AppleCake.mark(name .. ' -> ' .. tostring(subname))
  else
    AppleCake.mark(name)
  end
end

local function stopProfile(self, recursionDepth)
  local profileName, _ = _methodNameAndPath(self)
  profileName = _profileName(profileName, recursionDepth)
  local profile = _profiles(self)[profileName]
  profile:stop()
end


ProfiledObject = {
  startProfile = startProfile,
  stopProfile = stopProfile,
  trace = startProfile,
  untrace = stopProfile,
  mark = mark,
}

ProfiledObject.includeIn = function(klass, opts)
  klass.profiledFieldNames = opts.fields or {}
  klass:include(ProfiledObject)
end

AppleCake itself is impressively low-overhead, so I tried to keep the code light here and to have it not do too much unnecessary work.


Feel free to close the issue as I'm not sure it's really actionable, but again - I thought it was worth sharing

@EngineerSmith EngineerSmith added the enhancement New feature or request label Oct 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants